From patchwork Fri Mar 29 01:53:16 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609878 Received: from mail-yw1-f201.google.com (mail-yw1-f201.google.com [209.85.128.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 27DCB1CD38 for ; Fri, 29 Mar 2024 01:54:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677244; cv=none; b=mteWme8ioADOE0vLnx41EGYyKsT9qsl9BrF8ZaHsYnePuldP/DcxSN6SawXgpq2+HT+usFpJ4yhpwFhbPm5TVL9hvbBA88KkE6PsM6YQHaMVbzlPzTQcrWv/pv/sWaxhJnKPIJXB2ieqWyQcx609SvKLIgZAnvgx0Gvw2ceFES8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677244; c=relaxed/simple; bh=COTMQsswrF0FRMT13SRuAVp3LcxDhFLtqlaO5tjK0pg=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=jbUf1qKhX2w3ruNFW+rELxggLPSSyHjuWbrJlolXSci0K6kW0QeWEAD8CATG0ju5WesV6PHAU/qbgymBqRbDCHnVZbZVecwgLaejSFnk2ao/LJVNHVNHeB1TLugL4JSP+n5I4UbC6vGQ5OEPuh2e9AA0txYEgDyZ2v5pBRM9MOA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=JMbkzifS; arc=none smtp.client-ip=209.85.128.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="JMbkzifS" Received: by mail-yw1-f201.google.com with SMTP id 00721157ae682-614400d9f35so3431177b3.3 for ; Thu, 28 Mar 2024 18:54:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677242; x=1712282042; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=zuG/mHGKrSIUtx4w6cEfQTJMQYadvua3gQmJfgk1lpo=; b=JMbkzifSdQbpTkTqfHNkqnaRj+wpfquBvXHY8dyVo2yT8jVN7RReBnh86V0Vzv7pOT 1Z+B7LJKxNIldSDuPZzmhQuwOSH7Q2+HqEh9wtGPSCHKz+6U8kWx5jz9bXvIFyX2wgNK PpjOlfHzxmKVClgNJXQ1kxEex9CHQgMjGl+IRI5NSHq+VlLODdw8YC+DZ8cn3yaIhBxp RWTjoPy7H+7CDrOZ38IQjeYf0mFavZ8B2QrxsAa8VdY2qAhqW7+/nYKkNjJBtiB2JZUd ywF3NY8v1LJdJPO1Tr0E7YsrSpWQKN0VYB8ywjJgRWBHdeT+9MwX+AoPTpp8XZKuYigC BKWw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677242; x=1712282042; 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=zuG/mHGKrSIUtx4w6cEfQTJMQYadvua3gQmJfgk1lpo=; b=k+684yervCXqTC8/U6bEl+vdmwMf2XGvkgnXmNLzEqacfbImFTIhCxaTMGCWY1t13r f2gaYozMP408H6b0mPywoMNSAiOCGlqkCBxieThryn19bkx+A79XrynFeuvOKJU/PSNf zwMouCj03k5f/5jF56WDt8ju59kpKfYMRLrSpZgGb8Y4yiwl0G16ma1T8dDBrRxxuWjP sm3ZF9KZPLmSyA2fvvOYp+w59u9DXY6AnJU4bn3HqETZ2bDU53/9EAH7YJqDGvnkO9Hz +OLG/RVDkWGH8BojP53rhZudbpY7cmeoSJaLaop6hBnZ4EChGssW9tP1dZZrA4bMdWPf 5Sfw== X-Forwarded-Encrypted: i=1; AJvYcCWISjaZwzg2VPzdqferKMH/UsV2UjHHzWeT2UZCcn59ibC7P4oMAZvWfVigi/htwP1sES4gu6uDbVK4xML3HVPkM8SU X-Gm-Message-State: AOJu0YwgbPSuw7EagdWvjJsVFHFEvzNbUunhmlWbO9LalHJ+6n3z4E7b yKMAqHHlYUdc5sTQPS+vuzADrQLzV4P+ulRVOZ8fyr8xs+4dlQqPY3Gbl3E+ZSL02eiMxtuRxdK PuQ== X-Google-Smtp-Source: AGHT+IED/jGR9FMIZIsjwVV243BUIl4S9ew034aRBgTPJL//6If/s8rpgztWDMC7HEN1dk/qJiRztH6LPzI= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a81:52ca:0:b0:609:25d1:ea9a with SMTP id g193-20020a8152ca000000b0060925d1ea9amr309533ywb.9.1711677242218; Thu, 28 Mar 2024 18:54:02 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:16 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-2-drosen@google.com> Subject: [RFC PATCH v4 01/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC 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 e7418d15fe39..0c0a9a8b5c26 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -654,6 +654,29 @@ struct fuse_entry_out { struct fuse_attr attr; }; +#define FUSE_BPF_MAX_ENTRIES 2 + +enum fuse_bpf_type { + FUSE_ENTRY_BACKING = 1, + FUSE_ENTRY_BPF = 2, + FUSE_ENTRY_REMOVE_BACKING = 3, + FUSE_ENTRY_REMOVE_BPF = 4, +}; + +#define BPF_FUSE_NAME_MAX 15 + +struct fuse_bpf_entry_out { + uint32_t entry_type; + uint32_t unused; + union { + struct { + uint64_t unused2; + uint64_t fd; + }; + char name[BPF_FUSE_NAME_MAX + 1]; + }; +}; + struct fuse_forget_in { uint64_t nlookup; }; From patchwork Fri Mar 29 01:53:17 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609879 X-Patchwork-Delegate: bpf@iogearbox.net Received: from mail-yb1-f202.google.com (mail-yb1-f202.google.com [209.85.219.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 61DC021100 for ; Fri, 29 Mar 2024 01:54:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677246; cv=none; b=QYRMuTRcluHCQkUQ7uWI5AWvkpjhSgXHlAzxNqp78q4VoTtvVjz8qlHxybAxLMCqhrnd0BYe4Q/brdWWYt8pwPE0FqYadga9gAHEVBwV6JpHNao5SHOmNcvEnVI9OjbdyHO6I9y9b3MlvR5QFU/G9MW0BR+6iVS6CMZxVaIMPhk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677246; c=relaxed/simple; bh=Wc6g5oUwOwJMcy8zr/T7dnynkvOgSwhcOrIXewXrDos=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=teO9D3AS3Gpn/wFqvZD3ZBVb0dRcp+qn47MR9VAovUX8EadTTuESYFmzkRrAqwDGrQi+I7zjQB8NptpD6azS0u9szu945tJHp/uXquw9MT602916tZvfYiGsMndsL7nfp6D6fpL4G3vw+6+VKJbF3dv/fYhq1uYvATWnLWIQwVM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=VVRlV2AB; arc=none smtp.client-ip=209.85.219.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="VVRlV2AB" Received: by mail-yb1-f202.google.com with SMTP id 3f1490d57ef6-dcbee93a3e1so2390291276.3 for ; Thu, 28 Mar 2024 18:54:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677244; x=1712282044; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=/zH4C1QJ9QRhJsDuWqk5xHws80AiBqqvhiaKJmoPnqo=; b=VVRlV2AB4xLteIv7NdqAcWA487whBLy522o5Uoqki3PBN2l1tVi7VZYIMv36wcWfIF 2h6DavLDcwkX20y7dV3tKxoRF3V5gulrKAGtDonIc/bi/Y/IUO8H+dhjq9iMFgx/WafB o1DlV31/hO1e90foGbMyYrix1k9czDEXIN99mRV9RrZwcTvm2zbcQRYsasropiDwxwnq rM9+aVFAuwEZUFPySGXXQjrGOr4U9w2heyUYBc0kn48DWmWqLHD1JpZkElvBMDSHj8Iz 8kIox+T7X/e2otUw1nqqODbYcx3+3jgEDe5Z6UGb0zVa4NUQLXSWBylVRYjnNDpEjPWq O/2A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677244; x=1712282044; 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=/zH4C1QJ9QRhJsDuWqk5xHws80AiBqqvhiaKJmoPnqo=; b=snl4I+yPx8uktQIVY5mAQ+pGTnu5ZlN5tgkBLSvpqjOF81kpPkMGl8xzCxP9AVn17b 6Wf6DLhj481ftK0m0P1C0VYNUYB4bXFCMOShkhT0pIu7nMpm0VNvvxh3RayeznI+xF5k 6a+2fDY/R3/OpydPlUIkq7e9pkByaezgb6gV/Mb9utLncuThluWV/oXlqSgmqqX1FE5D WQ9CX3hPYhw7Szvu2yHRud+rjONheeKGm5nu4XlS2N9bWzRO7PovPlCnu/IcNTZjP5/8 0h70uCp6fwSjnT+QLu9q1FOrr9cCmGnhHXs54rnveBV+MicOM4N9sVOxcrU5OdceVLbu KgQg== X-Forwarded-Encrypted: i=1; AJvYcCWw4S61ACxhCht+7Q7wGJrshVFYx2nJfI7y89H18T+2keLRpTezwr9X951WBE0MsSZ7kaS+IQtRiUrps8ngP2IKCRMC X-Gm-Message-State: AOJu0YxXvV4S7UAB956ejua5OgPq/GtjyvDp126TcV39SEoorGuUp0+G QmHyQU8ilP4VEJ3kTGyP0+e7nUwzjuCsWrgA+ZoQwpD9jztwBzg+yP55KZQtdPmAVbAMMrq944m 6Jg== X-Google-Smtp-Source: AGHT+IECUo0Pk+Dy4BEAGGBe6rMjNQ00Kce7FlbJjQhechcikFYw4a3/fGgjQkC9x+edNOdwWHxstE0cb78= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:e0f:b0:dc6:44d4:bee0 with SMTP id df15-20020a0569020e0f00b00dc644d4bee0mr85414ybb.7.1711677244476; Thu, 28 Mar 2024 18:54:04 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:17 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-3-drosen@google.com> Subject: [RFC PATCH v4 02/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-Delegate: bpf@iogearbox.net X-Patchwork-State: RFC 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 buffers. + * 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 (growth requires alloc) +#define BPF_FUSE_MUST_ALLOCATE (1 << 2) // Buffer must be re allocated before allowing writes + +// Flags set by helper function +#define BPF_FUSE_MODIFIED (1 << 3) // The helper function allowed writes to the buffer +#define BPF_FUSE_ALLOCATED (1 << 4) // The helper function allocated the buffer + +/* + * BPF Fuse Args + * + * Used to translate between bpf program parameters and their userspace equivalent 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 */ From patchwork Fri Mar 29 01:53:18 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609880 Received: from mail-yb1-f202.google.com (mail-yb1-f202.google.com [209.85.219.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 81C032B9AD for ; Fri, 29 Mar 2024 01:54:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677249; cv=none; b=E6uWhCe+2m1cAavk/89m67emWR6oUqcbLhAokkvmrFsRFBJjbMoimdW3McNVejjyACM0t6dHlYBja4zrNIxt71GxDLAmmkI3HsqqdSdljIvEmabWjh7YyVV1lZBW3XunBVhEl8W8mApZ83kgv2Keb/qA83EQUAKP+G92cacOPKA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677249; c=relaxed/simple; bh=WHF1cQ6wftLpEUkLh/arn81XtKeZBIuOooiwZcqzz24=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=l2Wg4Nr1FSEQQBeJ/dPVZqy07SvnPS3bNyjTgCluKgsiljrgFeYJfRHqHNjysfxRrtvxKYrHC3BWdBhFVupaDnXDR9+DI+T0iNFk1D0mrxK9X19OZ/woqRhb9t26ZZjqVWXWs7g+ZWexk4bxt9OKyUPEMSDyJ7kddY9GXfgwm3M= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=ohkuFqn0; arc=none smtp.client-ip=209.85.219.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="ohkuFqn0" Received: by mail-yb1-f202.google.com with SMTP id 3f1490d57ef6-dc6b26845cdso2314391276.3 for ; Thu, 28 Mar 2024 18:54:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677246; x=1712282046; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=1TWHZ6CjfZzMP0P6lR3s6R//pEvlviDj7MeL1JlKB90=; b=ohkuFqn0UVk+BjOZ6e/RSq+48ILEeFYpINfxRwudlqaBWyFbnkpPawUTBv2CuqMfD1 nKkruG6I9j8NGgSA9NVtg7HFFnTUqmFC29sod1XHXTCBRYdW3S2Ir/T+QbLZT3239gae E3pKZvFzV7WBF8aL/cfmUln6QiwAysATkK9SOwcL7Y/LT4r3vpzE+dtbvj3ImOHvxzcO p5GdAoDK2j2Pj/oOVRZ4bHmUl77HwudnBv7cMP6VYmH3vegQkSeCFVq3Buqg+0w5BDAN bBklfy6rYVEQEw1nKMN8V3/ButYEI+bfHgut2Ye4IsUsZ1OlhTZKMBSPur38HYhTtAv9 vutA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677246; x=1712282046; 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=1TWHZ6CjfZzMP0P6lR3s6R//pEvlviDj7MeL1JlKB90=; b=PAcE0S1kLxwxJwv4NC+vloOP+gx1L+eJoeS0N3eN7+n5xIGRb2ygzf9v7ScYFfz4zI 8/xNSN5ClOZEFZD1E/1//W/ooWn5KV/FtZTelrjlxNDzL1tHRlPi0yHy7uSSPyxGGlFU 6LlMamFt+Y9ZpTkGv0LZknUO9hZYyYIuTv6k7IyZ+WZvXU3CfexDvfnWvtxid/riD1D+ EhdH7DSgoLIqXyLYw2iXSAQSSTyrUrwYGVeRK1woDIaz7PktWFgHgpRKcMvbvo5cb5Vz pKlXGliKXcxfAs80FeQI0ZpQFyG4pcwgQvcq49hVl0nbK6X2FNGdEa6/FHEjt5sNcNsa FR1Q== X-Forwarded-Encrypted: i=1; AJvYcCXRCS/FjRwrzkRBV2Tr8yqgosKvImpZ7TTCof3fyWBHeWQV5NdzlfemyT5YmFYNcmSsgou9WfFtEYuXcpBlRBzZ/OfS X-Gm-Message-State: AOJu0Yzz5SMpjf0XTssS+rdG7GiR03aXuV9cs467amoPzBGbfg6OokCG gC2L6uGGUjwfUtePj7ZC6HDBOJYyccnWVx1PrzfW97Pc356XbYwWcSbr5OdpeiaS1g/Z7F0BHDA 9/w== X-Google-Smtp-Source: AGHT+IEi4wBl0taMuZM199uicOLSQKEshdiXi8Hiat+CV3OzSJuBGo9P6id8BJm2lQgMqafkSWILsniPSE4= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:72c:b0:dc2:3441:897f with SMTP id l12-20020a056902072c00b00dc23441897fmr300435ybt.6.1711677246590; Thu, 28 Mar 2024 18:54:06 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:18 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-4-drosen@google.com> Subject: [RFC PATCH v4 03/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC 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 | 4 ---- fs/fuse/fuse_i.h | 8 ++++++++ fs/fuse/inode.c | 52 ++++++++++++++++++++++++------------------------ 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index d19cbf34c634..cd8d6b2f6d78 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) } #else -union fuse_dentry { - u64 time; - struct rcu_head rcu; -}; static inline void __fuse_dentry_settime(struct dentry *dentry, u64 time) { diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index bcbe34488862..797bcfd6fa06 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -76,6 +76,14 @@ struct fuse_submount_lookup { struct fuse_forget_link *forget; }; +/** 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 */ diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 516ea2979a90..8eff618ac47b 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -197,6 +197,32 @@ static ino_t fuse_squash_ino(u64 ino64) return ino; } +static void fuse_fill_attr_from_inode(struct fuse_attr *attr, + const struct fuse_inode *fi) +{ + struct timespec64 atime = inode_get_atime(&fi->inode); + struct timespec64 mtime = inode_get_mtime(&fi->inode); + struct timespec64 ctime = inode_get_ctime(&fi->inode); + + *attr = (struct fuse_attr){ + .ino = fi->inode.i_ino, + .size = fi->inode.i_size, + .blocks = fi->inode.i_blocks, + .atime = atime.tv_sec, + .mtime = mtime.tv_sec, + .ctime = ctime.tv_sec, + .atimensec = atime.tv_nsec, + .mtimensec = mtime.tv_nsec, + .ctimensec = ctime.tv_nsec, + .mode = fi->inode.i_mode, + .nlink = fi->inode.i_nlink, + .uid = fi->inode.i_uid.val, + .gid = fi->inode.i_gid.val, + .rdev = fi->inode.i_rdev, + .blksize = 1u << fi->inode.i_blkbits, + }; +} + void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr, struct fuse_statx *sx, u64 attr_valid, u32 cache_mask) @@ -1477,32 +1503,6 @@ void fuse_dev_free(struct fuse_dev *fud) } EXPORT_SYMBOL_GPL(fuse_dev_free); -static void fuse_fill_attr_from_inode(struct fuse_attr *attr, - const struct fuse_inode *fi) -{ - struct timespec64 atime = inode_get_atime(&fi->inode); - struct timespec64 mtime = inode_get_mtime(&fi->inode); - struct timespec64 ctime = inode_get_ctime(&fi->inode); - - *attr = (struct fuse_attr){ - .ino = fi->inode.i_ino, - .size = fi->inode.i_size, - .blocks = fi->inode.i_blocks, - .atime = atime.tv_sec, - .mtime = mtime.tv_sec, - .ctime = ctime.tv_sec, - .atimensec = atime.tv_nsec, - .mtimensec = mtime.tv_nsec, - .ctimensec = ctime.tv_nsec, - .mode = fi->inode.i_mode, - .nlink = fi->inode.i_nlink, - .uid = fi->inode.i_uid.val, - .gid = fi->inode.i_gid.val, - .rdev = fi->inode.i_rdev, - .blksize = 1u << fi->inode.i_blkbits, - }; -} - static void fuse_sb_defaults(struct super_block *sb) { sb->s_magic = FUSE_SUPER_MAGIC; From patchwork Fri Mar 29 01:53:19 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609881 Received: from mail-yw1-f202.google.com (mail-yw1-f202.google.com [209.85.128.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 86C513309E for ; Fri, 29 Mar 2024 01:54:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677252; cv=none; b=AzFofu0IV0+g/G3CpbYzhHMq5pVGxRSWg46nP73doYHQrvXNfAc6dqvy1yMsFUkaojsjNf7O4CGCV8jx6kp3pJvEdFz4/Fbudo4eYGFG7T8vmjZ7Hstb8uIIcdpHmyGb3/AN3bTUcpqVzFZLdNz+nPXHyWquDCAFvqf507emkAM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677252; c=relaxed/simple; bh=HwibsSMFiRAR0fnshHvGFB5IbKiOaqSe+Y1UKWsx8cU=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=ivXSlZ2tJPhhHvQFnPaiPAKlORXB3ZIlOKqrKe3ZtDYq3xdkkE5HlZofGOaZRBHSixzEK/lAc9/xue6AuwUtqwP8k+I221k6o5VZylvTl2BOW+Y8nh+ZW3Op16TRt1een0q8wWlv528A8fynZWcQoU0W9fV3kXriDOpByI9xnh8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=aQ8lLSkF; arc=none smtp.client-ip=209.85.128.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="aQ8lLSkF" Received: by mail-yw1-f202.google.com with SMTP id 00721157ae682-60a55cd262aso26554557b3.2 for ; Thu, 28 Mar 2024 18:54:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677248; x=1712282048; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=jHzI1Uvt7hSz2zKDPtn1Czmy96ylAwQFOSMTDSDsZ5g=; b=aQ8lLSkFnHH8czOTuJ/x3KM9DjXCEcBC5Zou6fEqWpexEwNYCbY2EifSJlYTTsSu4v +ft/IhDvmMd/3l9Ugu1LQ63p+MGxmA3iVBm5VcExsVAbWbHkk0lkJIupsHvEulsu5BzT 8XT6WdMl3fvEGaF07n1nePFO76k1eJsFgoik3jo9aWOoETcKDgm8Jad963SBkospLxJT +6C9kyTSqeTW+U31lCSqqYxVVBHvZA7kMfDVnGe4UEd7utiBBDkREMnnd3CCjo2a3Lws hBH6mb5BGm/B6wSAjbY+EwnZCkgcC1Y8IvzeRSNLiHahc0vQvFDe0xsBGNqpDPXT/GLn qOXg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677248; x=1712282048; 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=jHzI1Uvt7hSz2zKDPtn1Czmy96ylAwQFOSMTDSDsZ5g=; b=mrzw9vj4HE67m+pe8ToNP/ZL/o8cty0XijDS6/G4RkOzpChaBUW/v8EqWVPiQVGFzP rKBHiyx/8iO4uSZsNsipjl3SXdPjnluGJ6G/nXqXA6bbBDWq8lT5wY17Us9Mkulu9C3/ NLD0lEKZHgu8e96BaQ5YY4cOX4MpIzMXv7JL4xj++Aox4QWnqOHGZEpkG38UBLaM+nxS +VZCtiMzUYoSuQ8oF7y9p0hrCFXIDqIC4vfC1f99endX8L265cken5hT7uEgukqiVlOq na/cqsUmuABGia+oLvqiDrKCQl7cBc5M0IgqEEr7iVuJBlHThDqHeipUkcaKPtYSP5hC 0xrQ== X-Forwarded-Encrypted: i=1; AJvYcCVWrccznbThDeXfEILZGx3qjWeKByT+BIQSYc9Vzfz5PrtaLId22z6zywCdl+33rTJnvh9x9V9l4UGmDL0hqXngNiZa X-Gm-Message-State: AOJu0Yw5fP93xZQXUkeNXNu46WIUgwEptXTapVWFH4s0/GhCEBseidzE J4W2KETUYlCXp6j94PU/aKq1rp0DunrBhmditd38veVPYktHwjqYN09S6IHuG3H44WV7o3iZ2Tf 1IA== X-Google-Smtp-Source: AGHT+IG0Zlr5ZkyPEVA/AdJ/e7E7t3v1uduc7lU0aEjzNyLpgzPVabwHLLc+66UK1SBfIYar8i49KtRyZZI= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a81:bb52:0:b0:613:eaaa:9288 with SMTP id a18-20020a81bb52000000b00613eaaa9288mr308507ywl.5.1711677248572; Thu, 28 Mar 2024 18:54:08 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:19 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-5-drosen@google.com> Subject: [RFC PATCH v4 04/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence , Alessio Balsini X-Patchwork-State: RFC 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 | 422 ++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dev.c | 41 ++++- fs/fuse/dir.c | 184 ++++++++++++++++---- fs/fuse/file.c | 25 ++- fs/fuse/fuse_i.h | 100 ++++++++++- fs/fuse/inode.c | 183 ++++++++++++++++---- fs/fuse/ioctl.c | 2 +- 9 files changed, 887 insertions(+), 79 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 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) += virtiofs.o fuse-y := dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o fuse-$(CONFIG_FUSE_DAX) += dax.o +fuse-$(CONFIG_FUSE_BPF) += backing.o virtiofs-y := virtio_fs.o diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c new file mode 100644 index 000000000000..14fcc2032764 --- /dev/null +++ b/fs/fuse/backing.c @@ -0,0 +1,422 @@ +// 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 point 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...): function that sets + * up fa and io based on args + * void initialize_out(struct bpf_fuse_args *fa, io *in_out, args...): function 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 = get_fuse_inode(inode); \ + struct bpf_fuse_args fa = { 0 }; \ + bool initialized = false; \ + bool handled = false; \ + ssize_t res; \ + io feo = { 0 }; \ + int error = 0; \ + \ + do { \ + if (!inode || !fuse_inode->backing_inode) \ + break; \ + \ + handled = true; \ + error = initialize_in(&fa, &feo, args); \ + if (error) \ + break; \ + \ + error = initialize_out(&fa, &feo, args); \ + if (error) \ + break; \ + \ + initialized = true; \ + \ + error = backing(&fa, out, args); \ + if (error < 0) \ + fa.info.error_in = error; \ + \ + } while (false); \ + \ + if (initialized && handled) { \ + res = finalize(&fa, out, args); \ + if (res) \ + error = res; \ + } \ + \ + *out = 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 = file->f_path; +} + +static bool has_file(int type) +{ + return type == 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 backing + * 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 = false; + int num_entries; + int err = -EINVAL; + int i; + + if (num > 0) + num_entries = num; + else + num_entries = FUSE_BPF_MAX_ENTRIES; + + for (i = 0; i < num_entries; i++) { + file = NULL; + fbeo = &fbe->out[i]; + + /* reserved for future use */ + if (fbeo->unused != 0) + goto out_err; + + if (has_file(fbeo->entry_type)) { + file = fget(fbeo->fd); + if (!file) { + err = -EBADF; + goto out_err; + } + } + + switch (fbeo->entry_type) { + case 0: + if (num == -1) + num_entries = i; + else + goto out_err; + break; + case FUSE_ENTRY_REMOVE_BACKING: + if (fbe->backing_action) + goto out_err; + fbe->backing_action = 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 = FUSE_BPF_SET; + has_backing = true; + break; + default: + err = -EINVAL; + goto out_err; + } + if (has_file(fbeo->entry_type)) { + fput(file); + file = NULL; + } + } + + fbe->is_used = 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)) { + struct timespec64 mtime = inode_get_mtime(inode); + struct timespec64 ctime = inode_get_ctime(inode); + + stat->size = i_size_read(inode); + stat->mtime.tv_sec = mtime.tv_sec; + stat->mtime.tv_nsec = mtime.tv_nsec; + stat->ctime.tv_sec = ctime.tv_sec; + stat->ctime.tv_nsec = ctime.tv_nsec; + } + + attr->ino = stat->ino; + attr->mode = (inode->i_mode & S_IFMT) | (stat->mode & 07777); + attr->nlink = stat->nlink; + attr->uid = from_kuid(fc->user_ns, stat->uid); + attr->gid = from_kgid(fc->user_ns, stat->gid); + attr->atime = stat->atime.tv_sec; + attr->atimensec = stat->atime.tv_nsec; + attr->mtime = stat->mtime.tv_sec; + attr->mtimensec = stat->mtime.tv_nsec; + attr->ctime = stat->ctime.tv_sec; + attr->ctimensec = stat->ctime.tv_nsec; + attr->size = stat->size; + attr->blocks = stat->blocks; + + if (stat->blksize != 0) + blkbits = ilog2(stat->blksize); + else + blkbits = inode->i_sb->s_blocksize_bits; + + attr->blksize = 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 = (struct fuse_lookup_args) { + .name = (struct fuse_buffer) { + .data = (void *) entry->d_name.name, + .size = entry->d_name.len + 1, + .max_size = NAME_MAX + 1, + .flags = BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, + }, + }; + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_fuse_inode(dir)->nodeid, + .opcode = FUSE_LOOKUP, + }, + .in_numargs = 1, + .in_args[0] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = &args->name, + }, + }; + + return 0; +} + +static int fuse_lookup_initialize_out(struct bpf_fuse_args *fa, struct fuse_lookup_args *args, + struct inode *dir, struct dentry *entry, unsigned int flags) +{ + args->bpf_entries = (struct fuse_buffer) { + .data = args->entries_storage.out, + .size = 0, + .alloc_size = sizeof(args->entries_storage.out), + .max_size = sizeof(args->entries_storage.out), + .flags = BPF_FUSE_VARIABLE_SIZE, + }, + + fa->out_numargs = 2; + fa->flags = FUSE_BPF_OUT_ARGVAR | FUSE_BPF_IS_LOOKUP; + fa->out_args[0] = (struct bpf_fuse_arg) { + .size = sizeof(args->out), + .value = &args->out, + }; + fa->out_args[1] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = &args->bpf_entries, + }; + + return 0; +} + +static int fuse_lookup_backing(struct bpf_fuse_args *fa, struct dentry **out, struct inode *dir, + struct dentry *entry, unsigned int flags) +{ + struct fuse_dentry *fuse_entry = get_fuse_dentry(entry); + struct fuse_dentry *dir_fuse_entry = get_fuse_dentry(entry->d_parent); + struct dentry *dir_backing_entry = dir_fuse_entry->backing_path.dentry; + struct inode *dir_backing_inode = dir_backing_entry->d_inode; + struct fuse_entry_out *feo = (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 = (char *)fa->in_args[0].buffer->data; + len = strnlen(name, fa->in_args[0].buffer->size); + } else { + name = entry->d_name.name; + len = entry->d_name.len; + } + backing_entry = 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 = (struct path) { + .dentry = backing_entry, + .mnt = mntget(dir_fuse_entry->backing_path.mnt), + }; + + if (d_is_negative(backing_entry)) { + fa->info.error_in = -ENOENT; + return 0; + } + + err = 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_path) +{ + 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 = fbe->backing_path; + fbe->backing_path.dentry = NULL; + fbe->backing_path.mnt = 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 = entry->d_inode; + struct fuse_entry_out *feo = fa->out_args[0].value; + struct fuse_bpf_entry_out *febo = fa->out_args[1].buffer->data; + struct fuse_bpf_entry *fbe = container_of(febo, struct fuse_bpf_entry, out[0]); + int error = -1; + u64 target_nodeid = 0; + + parse_fuse_bpf_entry(fbe, -1); + fd = get_fuse_dentry(entry); + if (!fd) + return -EIO; + error = fuse_handle_backing(fbe, &fd->backing_path); + if (error) + return error; + backing_dentry = fd->backing_path.dentry; + if (!backing_dentry) + return -ENOENT; + backing_inode = backing_dentry->d_inode; + if (!backing_inode) { + *out = 0; + return 0; + } + + if (d_inode) + target_nodeid = get_fuse_inode(d_inode)->nodeid; + + inode = fuse_iget_backing(dir->i_sb, target_nodeid, backing_inode); + + if (IS_ERR(inode)) + return PTR_ERR(inode); + + get_fuse_inode(inode)->nodeid = feo->nodeid; + + *out = 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 = get_fuse_dentry(entry); + struct dentry *backing_entry = 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 1a8f82f478cb..b413e0bfd61c 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -238,6 +238,11 @@ void fuse_queue_forget(struct fuse_conn *fc, struct fuse_forget_link *forget, { struct fuse_iqueue *fiq = &fc->iq; + if (nodeid == 0) { + kfree(forget); + return; + } + forget->forget_one.nodeid = nodeid; forget->forget_one.nlookup = nlookup; @@ -1009,10 +1014,38 @@ static int fuse_copy_one(struct fuse_copy_state *cs, void *val, unsigned size) return 0; } +/* 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, unsigned size) +{ + struct fuse_bpf_entry_out *fbeo = (struct fuse_bpf_entry_out *)val; + struct fuse_bpf_entry *feb = container_of(fbeo, struct fuse_bpf_entry, out[0]); + int num_entries = size / sizeof(*fbeo); + int err; + + if (size && size % sizeof(*fbeo) != 0) + return -EINVAL; + + if (num_entries > FUSE_BPF_MAX_ENTRIES) + return -EINVAL; + err = fuse_copy_one(cs, val, size); + if (err) + return err; + if (size) + err = parse_fuse_bpf_entry(feb, num_entries); + return err; +} +#else +static int fuse_copy_lookup(struct fuse_copy_state *cs, void *val, unsigned 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 = 0; unsigned i; @@ -1021,6 +1054,8 @@ static int fuse_copy_args(struct fuse_copy_state *cs, unsigned numargs, struct fuse_arg *arg = &args[i]; if (i == numargs - 1 && argpages) err = fuse_copy_pages(cs, arg->size, zeroing); + else if (i == numargs - 1 && is_lookup) + err = fuse_copy_lookup(cs, arg->value, arg->size); else err = 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 = fuse_copy_one(cs, &req->in.h, sizeof(req->in.h)); if (!err) err = 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 -= 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); } /* diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index cd8d6b2f6d78..6503c91886f6 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); } -#if BITS_PER_LONG >= 64 +#if BITS_PER_LONG >= 64 && !defined(CONFIG_FUSE_BPF) static inline void __fuse_dentry_settime(struct dentry *entry, u64 time) { entry->d_fsdata = (void *) time; @@ -49,12 +49,12 @@ static inline u64 fuse_dentry_time(const struct dentry *entry) static inline void __fuse_dentry_settime(struct dentry *dentry, u64 time) { - ((union fuse_dentry *) dentry->d_fsdata)->time = time; + ((struct fuse_dentry *) dentry->d_fsdata)->time = time; } 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 @@ -111,6 +111,18 @@ void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o) fuse_time_to_jiffies(o->entry_valid, o->entry_valid_nsec)); } +void fuse_init_dentry_root(struct dentry *root, struct file *backing_dir) +{ +#ifdef CONFIG_FUSE_BPF + struct fuse_dentry *fuse_dentry = root->d_fsdata; + + if (backing_dir) { + fuse_dentry->backing_path = backing_dir->f_path; + path_get(&fuse_dentry->backing_path); + } +#endif +} + void fuse_invalidate_attr_mask(struct inode *inode, u32 mask) { set_mask_bits(&get_fuse_inode(inode)->inval_mask, 0, mask); @@ -166,7 +178,8 @@ static void fuse_invalidate_entry(struct dentry *entry) 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 = FUSE_LOOKUP; @@ -174,10 +187,43 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args, args->in_numargs = 1; args->in_args[0].size = name->len + 1; args->in_args[0].value = name->name; - args->out_numargs = 1; + args->out_argvar = true; + args->out_numargs = 2; args->out_args[0].size = sizeof(struct fuse_entry_out); args->out_args[0].value = outarg; + args->out_args[1].size = sizeof(struct fuse_bpf_entry_out) * FUSE_BPF_MAX_ENTRIES; + args->out_args[1].value = bpf_outarg; + args->is_lookup = 1; +} + +#ifdef CONFIG_FUSE_BPF +static bool backing_data_changed(struct fuse_inode *fi, struct dentry *entry, + struct fuse_bpf_entry *bpf_arg) +{ + struct path new_backing_path; + struct inode *new_backing_inode; + int err; + bool ret = true; + + if (!entry) + return false; + + get_fuse_backing_path(entry, &new_backing_path); + + err = fuse_handle_backing(bpf_arg, &new_backing_path); + new_backing_inode = d_inode(new_backing_path.dentry); + + if (err) + goto put_inode; + + ret = (fi->backing_inode != new_backing_inode || + !path_equal(&get_fuse_dentry(entry)->backing_path, &new_backing_path)); + +put_inode: + path_put(&new_backing_path); + return ret; } +#endif /* * Check whether the dentry is still valid @@ -199,9 +245,23 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags) inode = 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 shouldn't + * try to fix that without going through the normal lookup path... + */ + if (get_fuse_dentry(entry)->backing_path.dentry) { + ret = fuse_revalidate_backing(entry, flags); + if (ret <= 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; @@ -213,27 +273,44 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags) ret = -ECHILD; if (flags & LOOKUP_RCU) goto out; - fm = get_fuse_mount(inode); + parent = 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 = 1; + goto out; + } +#endif forget = fuse_alloc_forget(); ret = -ENOMEM; - if (!forget) + if (!forget) { + dput(parent); goto out; + } attr_version = fuse_get_attr_version(fm->fc); - parent = 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 = fuse_simple_request(fm, &args); dput(parent); + /* Zero nodeid is same as -ENOENT */ if (!ret && !outarg.nodeid) ret = -ENOENT; - if (!ret) { + if (!ret || bpf_arg.is_used) { fi = get_fuse_inode(inode); if (outarg.nodeid != get_node_id(inode) || +#ifdef CONFIG_FUSE_BPF + (bpf_arg.is_used && + backing_data_changed(fi, entry, &bpf_arg)) || +#endif (bool) IS_AUTOMOUNT(inode) != (bool) (outarg.attr.flags & FUSE_ATTR_SUBMOUNT)) { fuse_queue_forget(fm->fc, forget, outarg.nodeid, 1); @@ -275,17 +352,20 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags) goto out; } -#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 = kzalloc(sizeof(union fuse_dentry), + dentry->d_fsdata = kzalloc(sizeof(struct fuse_dentry), GFP_KERNEL_ACCOUNT | __GFP_RECLAIMABLE); return dentry->d_fsdata ? 0 : -ENOMEM; } static void fuse_dentry_release(struct dentry *dentry) { - union fuse_dentry *fd = dentry->d_fsdata; + struct fuse_dentry *fd = dentry->d_fsdata; + + if (fd && fd->backing_path.dentry) + path_put(&fd->backing_path); kfree_rcu(fd, rcu); } @@ -326,7 +406,7 @@ static struct vfsmount *fuse_dentry_automount(struct path *path) const struct dentry_operations fuse_dentry_operations = { .d_revalidate = fuse_dentry_revalidate, .d_delete = fuse_dentry_delete, -#if BITS_PER_LONG < 64 +#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_BPF) .d_init = fuse_dentry_init, .d_release = fuse_dentry_release, #endif @@ -334,7 +414,7 @@ const struct dentry_operations fuse_dentry_operations = { }; const struct dentry_operations fuse_root_dentry_operations = { -#if BITS_PER_LONG < 64 +#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_BPF) .d_init = fuse_dentry_init, .d_release = fuse_dentry_release, #endif @@ -356,11 +436,13 @@ bool fuse_invalid_attr(struct fuse_attr *attr) return !fuse_valid_type(attr->mode) || !fuse_valid_size(attr->size); } -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 = get_fuse_mount_super(sb); FUSE_ARGS(args); + struct fuse_bpf_entry bpf_arg = { 0 }; struct fuse_forget_link *forget; u64 attr_version; int err; @@ -370,7 +452,6 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name if (name->len > FUSE_NAME_MAX) goto out; - forget = fuse_alloc_forget(); err = -ENOMEM; if (!forget) @@ -378,21 +459,53 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name attr_version = fuse_get_attr_version(fm->fc); - fuse_lookup_init(fm->fc, &args, nodeid, name, outarg); + fuse_lookup_init(fm->fc, &args, nodeid, name, outarg, bpf_arg.out); err = fuse_simple_request(fm, &args); - /* Zero nodeid is same as -ENOENT, but with valid timeout */ - if (err || !outarg->nodeid) - goto out_put_forget; - err = -EIO; - if (fuse_invalid_attr(&outarg->attr)) - goto out_put_forget; +#ifdef CONFIG_FUSE_BPF + if (bpf_arg.is_used) { + /* TODO Make sure this handles invalid handles */ + struct path *backing_path; + struct inode *backing_inode; - *inode = fuse_iget(sb, outarg->nodeid, outarg->generation, - &outarg->attr, ATTR_TIMEOUT(outarg), - attr_version); - err = -ENOMEM; - if (!*inode) { + err = -ENOENT; + if (!entry) + goto out_put_forget; + + err = -EINVAL; + backing_path = &bpf_arg.backing_path; + if (!backing_path->dentry) + goto out_put_forget; + + err = fuse_handle_backing(&bpf_arg, + &get_fuse_dentry(entry)->backing_path); + if (err) + goto out_put_forget; + + backing_inode = d_inode(get_fuse_dentry(entry)->backing_path.dentry); + *inode = 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 = -EIO; + if (fuse_invalid_attr(&outarg->attr)) + goto out_put_forget; + + *inode = fuse_iget(sb, outarg->nodeid, outarg->generation, + &outarg->attr, ATTR_TIMEOUT(outarg), + attr_version); + err = -ENOMEM; + } +#ifdef CONFIG_FUSE_BPF +out_queue_forget: +#endif + if (!*inode && outarg->nodeid) { fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1); goto out; } @@ -417,9 +530,12 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, if (fuse_is_bad(dir)) return ERR_PTR(-EIO); + if (fuse_bpf_lookup(&newent, dir, entry, flags)) + return newent; + locked = fuse_lock_inode(dir); err = 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 == -ENOENT) { outarg_valid = false; @@ -1495,6 +1611,7 @@ static int fuse_permission(struct mnt_idmap *idmap, struct fuse_conn *fc = get_fuse_conn(inode); bool refreshed = false; int err = 0; + struct fuse_inode *fi = get_fuse_inode(inode); if (fuse_is_bad(inode)) return -EIO; @@ -1507,7 +1624,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 = get_fuse_inode(inode); u32 perm_mask = STATX_MODE | STATX_UID | STATX_GID; if (perm_mask & READ_ONCE(fi->inval_mask) || @@ -1684,7 +1800,7 @@ static long fuse_dir_compat_ioctl(struct file *file, unsigned int cmd, FUSE_IOCTL_COMPAT | FUSE_IOCTL_DIR); } -static bool update_mtime(unsigned ivalid, bool trust_local_mtime) +static inline bool update_mtime(unsigned int ivalid, bool trust_local_mtime) { /* 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 148a71b8b4d0..41a2e88e8646 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -8,6 +8,7 @@ #include "fuse_i.h" +#include #include #include #include @@ -127,13 +128,18 @@ static void fuse_file_put(struct fuse_file *ff, bool sync, bool isdir) } 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 = fm->fc; struct fuse_file *ff; int opcode = isdir ? FUSE_OPENDIR : FUSE_OPEN; - ff = fuse_file_alloc(fm); + if (file && file->private_data) { + ff = file->private_data; + file->private_data = NULL; + } else { + ff = fuse_file_alloc(fm); + } if (!ff) return ERR_PTR(-ENOMEM); @@ -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 = fuse_file_open(fm, nodeid, file->f_flags, isdir); + struct fuse_file *ff = fuse_file_open(fm, nodeid, file->f_flags, isdir, file); if (!IS_ERR(ff)) file->private_data = ff; @@ -1906,6 +1912,19 @@ int fuse_write_inode(struct inode *inode, struct writeback_control *wbc) */ WARN_ON(wbc->for_reclaim); + /** + * 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 = __fuse_write_file_get(fi); err = fuse_flush_times(inode, ff); if (ff) diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 797bcfd6fa06..ac61f08fd85d 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 /** Default max number of pages that can be used in a single read request */ #define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32 @@ -77,11 +80,35 @@ struct fuse_submount_lookup { }; /** 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 *entry) +{ + 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 = get_fuse_dentry(d); + + if (!di) { + *path = (struct path) { .mnt = 0, .dentry = 0 }; + return; + } + + *path = di->backing_path; + path_get(path); +} #endif /** FUSE inode */ @@ -89,6 +116,14 @@ struct fuse_inode { /** Inode data */ struct inode inode; +#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; @@ -246,6 +281,14 @@ struct fuse_file { } readdir; +#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; @@ -277,6 +320,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; @@ -291,6 +335,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); @@ -544,6 +589,7 @@ struct fuse_fs_context { unsigned int max_read; unsigned int blksize; const char *subtype; + struct file *root_dir; /* DAX device, may be NULL */ struct dax_device *dax_dev; @@ -997,12 +1043,16 @@ extern const struct dentry_operations fuse_root_dentry_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); 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); /** * Send FORGET command @@ -1154,6 +1204,8 @@ u64 fuse_time_to_jiffies(u64 sec, u32 nsec); void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o); +void fuse_init_dentry_root(struct dentry *root, struct file *backing_dir); + /** * Acquire reference to fuse_conn */ @@ -1360,8 +1412,44 @@ int fuse_fileattr_set(struct mnt_idmap *idmap, /* file.c */ 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); +/* backing.c */ + +enum fuse_bpf_set { + FUSE_BPF_UNCHANGED = 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_path); + +int fuse_revalidate_backing(struct dentry *entry, unsigned int flags); + #endif /* _FS_FUSE_I_H */ diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 8eff618ac47b..6570fe7a9b53 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -96,6 +96,9 @@ static struct inode *fuse_alloc_inode(struct super_block *sb) fi->i_time = 0; fi->inval_mask = ~0; +#ifdef CONFIG_FUSE_BPF + fi->backing_inode = NULL; +#endif fi->nodeid = 0; fi->nlookup = 0; fi->attr_version = 0; @@ -150,6 +153,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); +#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) { @@ -198,28 +205,28 @@ static ino_t fuse_squash_ino(u64 ino64) } static void fuse_fill_attr_from_inode(struct fuse_attr *attr, - const struct fuse_inode *fi) + const struct inode *inode) { - struct timespec64 atime = inode_get_atime(&fi->inode); - struct timespec64 mtime = inode_get_mtime(&fi->inode); - struct timespec64 ctime = inode_get_ctime(&fi->inode); + struct timespec64 atime = inode_get_atime(inode); + struct timespec64 mtime = inode_get_mtime(inode); + struct timespec64 ctime = inode_get_ctime(inode); *attr = (struct fuse_attr){ - .ino = fi->inode.i_ino, - .size = fi->inode.i_size, - .blocks = fi->inode.i_blocks, + .ino = inode->i_ino, + .size = inode->i_size, + .blocks = inode->i_blocks, .atime = atime.tv_sec, .mtime = mtime.tv_sec, .ctime = ctime.tv_sec, .atimensec = atime.tv_nsec, .mtimensec = mtime.tv_nsec, .ctimensec = ctime.tv_nsec, - .mode = fi->inode.i_mode, - .nlink = fi->inode.i_nlink, - .uid = fi->inode.i_uid.val, - .gid = fi->inode.i_gid.val, - .rdev = fi->inode.i_rdev, - .blksize = 1u << fi->inode.i_blkbits, + .mode = inode->i_mode, + .nlink = inode->i_nlink, + .uid = inode->i_uid.val, + .gid = inode->i_gid.val, + .rdev = inode->i_rdev, + .blksize = 1u << inode->i_blkbits, }; } @@ -415,8 +422,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(); /* @@ -427,22 +433,100 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr, inode->i_acl = inode->i_default_acl = ACL_DONT_CACHE; } +struct fuse_inode_identifier { + u64 nodeid; + struct inode *backing_inode; +}; + static int fuse_inode_eq(struct inode *inode, void *_nodeidp) { - u64 nodeid = *(u64 *) _nodeidp; - if (get_node_id(inode) == nodeid) - return 1; - else - return 0; + struct fuse_inode_identifier *fii = + (struct fuse_inode_identifier *) _nodeidp; + struct fuse_inode *fi = get_fuse_inode(inode); + + return fii->nodeid == fi->nodeid; +} + +static int fuse_inode_backing_eq(struct inode *inode, void *_nodeidp) +{ + struct fuse_inode_identifier *fii = + (struct fuse_inode_identifier *) _nodeidp; + struct fuse_inode *fi = get_fuse_inode(inode); + + return fii->nodeid == fi->nodeid +#ifdef CONFIG_FUSE_BPF + && fii->backing_inode == fi->backing_inode +#endif + ; } static int fuse_inode_set(struct inode *inode, void *_nodeidp) { - u64 nodeid = *(u64 *) _nodeidp; - get_fuse_inode(inode)->nodeid = nodeid; + struct fuse_inode_identifier *fii = + (struct fuse_inode_identifier *) _nodeidp; + struct fuse_inode *fi = get_fuse_inode(inode); + + fi->nodeid = fii->nodeid; + + return 0; +} + +static int fuse_inode_backing_set(struct inode *inode, void *_nodeidp) +{ + struct fuse_inode_identifier *fii = + (struct fuse_inode_identifier *) _nodeidp; + struct fuse_inode *fi = get_fuse_inode(inode); + + fi->nodeid = fii->nodeid; +#ifdef CONFIG_FUSE_BPF + BUG_ON(fi->backing_inode != NULL); + fi->backing_inode = fii->backing_inode; + if (fi->backing_inode) + ihold(fi->backing_inode); +#endif + return 0; } +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 = get_fuse_conn_super(sb); + struct fuse_inode_identifier fii = { + .nodeid = nodeid, + .backing_inode = backing_inode, + }; + struct fuse_attr attr; + unsigned long hash = (unsigned long) backing_inode; + + if (nodeid) + hash = nodeid; + + fuse_fill_attr_from_inode(&attr, backing_inode); + inode = 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 |= S_NOATIME; + if (!fc->writeback_cache) + inode->i_flags |= S_NOCMTIME; + fuse_init_common(inode); + unlock_new_inode(inode); + } + + fi = 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) @@ -450,6 +534,9 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid, struct inode *inode; struct fuse_inode *fi; struct fuse_conn *fc = get_fuse_conn_super(sb); + struct fuse_inode_identifier fii = { + .nodeid = nodeid, + }; /* * Auto mount points get their node id from the submount root, which is @@ -481,7 +568,7 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid, } retry: - inode = iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &nodeid); + inode = iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &fii); if (!inode) return NULL; @@ -513,13 +600,16 @@ struct inode *fuse_ilookup(struct fuse_conn *fc, u64 nodeid, { struct fuse_mount *fm_iter; struct inode *inode; + struct fuse_inode_identifier fii = { + .nodeid = nodeid, + }; WARN_ON(!rwsem_is_locked(&fc->killsb)); list_for_each_entry(fm_iter, &fc->mounts, fc_entry) { if (!fm_iter->sb) continue; - inode = ilookup5(fm_iter->sb, nodeid, fuse_inode_eq, &nodeid); + inode = ilookup5(fm_iter->sb, nodeid, fuse_inode_eq, &fii); if (inode) { if (fm) *fm = fm_iter; @@ -749,6 +839,7 @@ enum { OPT_ALLOW_OTHER, OPT_MAX_READ, OPT_BLKSIZE, + OPT_ROOT_DIR, OPT_ERR }; @@ -763,6 +854,7 @@ static const struct fs_parameter_spec fuse_fs_parameters[] = { fsparam_u32 ("max_read", OPT_MAX_READ), fsparam_u32 ("blksize", OPT_BLKSIZE), fsparam_string ("subtype", OPT_SUBTYPE), + fsparam_u32 ("root_dir", OPT_ROOT_DIR), {} }; @@ -846,6 +938,12 @@ static int fuse_parse_param(struct fs_context *fsc, struct fs_parameter *param) ctx->blksize = result.uint_32; break; + case OPT_ROOT_DIR: + ctx->root_dir = fget(result.uint_32); + if (!ctx->root_dir) + return invalfc(fsc, "Unable to open root directory"); + break; + default: return -EINVAL; } @@ -858,6 +956,8 @@ static void fuse_free_fsc(struct fs_context *fsc) struct fuse_fs_context *ctx = fsc->fs_private; if (ctx) { + if (ctx->root_dir) + fput(ctx->root_dir); kfree(ctx->subtype); kfree(ctx); } @@ -992,15 +1092,29 @@ struct fuse_conn *fuse_conn_get(struct fuse_conn *fc) } EXPORT_SYMBOL_GPL(fuse_conn_get); -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; + memset(&attr, 0, sizeof(attr)); attr.mode = mode; attr.ino = FUSE_ROOT_ID; attr.nlink = 1; - return fuse_iget(sb, 1, 0, &attr, 0, 0); + inode = 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 = backing_fd->f_inode; + ihold(backing_fd->f_inode); + } +#endif + + return inode; } struct fuse_inode_handle { @@ -1015,11 +1129,14 @@ static struct dentry *fuse_get_dentry(struct super_block *sb, struct inode *inode; struct dentry *entry; int err = -ESTALE; + struct fuse_inode_identifier fii = { + .nodeid = handle->nodeid, + }; if (handle->nodeid == 0) goto out_err; - inode = ilookup5(sb, handle->nodeid, fuse_inode_eq, &handle->nodeid); + inode = ilookup5(sb, handle->nodeid, fuse_inode_eq, &fii); if (!inode) { struct fuse_entry_out outarg; const struct qstr name = QSTR_INIT(".", 1); @@ -1028,7 +1145,7 @@ static struct dentry *fuse_get_dentry(struct super_block *sb, goto out_err; err = fuse_lookup_name(sb, handle->nodeid, &name, &outarg, - &inode); + NULL, &inode); if (err && err != -ENOENT) goto out_err; if (err || !inode) { @@ -1123,13 +1240,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 = QSTR_INIT("..", 2); int err; if (!fc->export_support) return ERR_PTR(-ESTALE); err = fuse_lookup_name(child_inode->i_sb, get_node_id(child_inode), - &dotdot_name, &outarg, &inode); + &name, &outarg, NULL, &inode); if (err) { if (err == -ENOENT) return ERR_PTR(-ESTALE); @@ -1541,7 +1659,7 @@ static int fuse_fill_super_submount(struct super_block *sb, if (parent_sb->s_subtype && !sb->s_subtype) return -ENOMEM; - fuse_fill_attr_from_inode(&root_attr, parent_fi); + fuse_fill_attr_from_inode(&root_attr, &parent_fi->inode); root = fuse_iget(sb, parent_fi->nodeid, 0, &root_attr, 0, 0); /* * This inode is just a duplicate, so it is not looked up and @@ -1685,11 +1803,12 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx) fc->no_force_umount = ctx->no_force_umount; err = -ENOMEM; - root = fuse_get_root_inode(sb, ctx->rootmode); + root = fuse_get_root_inode(sb, ctx->rootmode, ctx->root_dir); sb->s_d_op = &fuse_root_dentry_operations; root_dentry = 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 = &fuse_dentry_operations; diff --git a/fs/fuse/ioctl.c b/fs/fuse/ioctl.c index 726640fa439e..8929dfec4970 100644 --- a/fs/fuse/ioctl.c +++ b/fs/fuse/ioctl.c @@ -433,7 +433,7 @@ static struct fuse_file *fuse_priv_ioctl_prepare(struct inode *inode) if (!S_ISREG(inode->i_mode) && !isdir) return ERR_PTR(-ENOTTY); - 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); } static void fuse_priv_ioctl_cleanup(struct inode *inode, struct fuse_file *ff) From patchwork Fri Mar 29 01:53:20 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609882 Received: from mail-yb1-f201.google.com (mail-yb1-f201.google.com [209.85.219.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A2F9637169 for ; Fri, 29 Mar 2024 01:54:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677253; cv=none; b=Yt2hhU4DxO8GHxEnmdi3NhJC1ewHCOXnUutna7f6zoBO3pnaoXx3Ek1cbcxByGIvaYCqEUeKftJt2g/pqaGJCWyQjRsmKjx+8b7WV7+HEbS4MP5jF+zjP2FUATsmO8lYmXVNCYZtWKMQTZIuVpt3sOPHnigAuemL0EI63yn47pU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677253; c=relaxed/simple; bh=Ib5M0mE4oOOU/5nwAtZYmwkWiH40UcIcM9efdJ1OU34=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=a2hHOk5oVUu700Eq3CSHViISwkQ1H6OQzZ/0CYKmLxK6xRxOjBl5DOCYw+Bza9q2YkPghiU9be5lGvxSaosR2RpzK4fnl+Y/v81n6eoXWpGydrHDEh7GJo51BBl91UZW609uVdJeWmx1bM92ekzqId7V7nYtGtaqQwzrliR5KUk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=uCjZBU7q; arc=none smtp.client-ip=209.85.219.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="uCjZBU7q" Received: by mail-yb1-f201.google.com with SMTP id 3f1490d57ef6-dbe9e13775aso2513967276.1 for ; Thu, 28 Mar 2024 18:54:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677250; x=1712282050; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=rxMPvHmZ0NJBpckZ6zd2n+WU0SbXQuOXvLdtYgKifzc=; b=uCjZBU7qN3mc0RtSMPWB1HF43nsmquKuWZwCiMPQmW1bNx5+Yr86QSapo54ubxdWHm FnEvwZPTRGQW3aUHyh5+pxDyz/QcKv2V8P+P7h5osplxGCmSVRJ+WZx8rQBApuYD7lZ/ qrN/GDyIYtJOTQufZUehG5iYJX7r8IoUJ5LoCxJE9vna58PpT+vWgzJM52qe3WkB28b+ w+6GRKI8xy/8z5ZWWlxlslpn6clmPK9xqnme043p97zou5witIEteQrCLW4Kh8bvlEcO MR+gY9WeW6Q/gYEBrlwi0JvcIaqnAuNrUvXmEiD//xC22Ena2Uz5t9cwrWuNt69ix4h2 5LxA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677250; x=1712282050; 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=rxMPvHmZ0NJBpckZ6zd2n+WU0SbXQuOXvLdtYgKifzc=; b=vF47ZpLoWRkw7DS0j1MPD1MwUfTQzp2TaE0IB+v4HVOT9Hd93SevU3jyKbgWOef4wP oF7RV8C2Ir8H6yOib7MvlCF7yDb/YtGqwaHMFDnTyfZDw1zs31GKdEzwsJyZ77dfiDZs gw/n+uGkjxUIhXCYnkjvG9qJgyigHADtgCJ00bJ3fn1fr/vdr8ofOXXNpfpYNoT0Vi4C fKcQpVdhGOCh3soEPMsmiifDH1O56tESiWVsDy4HZ00dkwTaq0ZCteJauOGAQ2eC6PVr exi8PcRNA7IPODl5vCmaUuUnOcqBP7eqtdLPFtVZnDTZt4HuDPC0A4M0EAwMBzD6y7CF qmOA== X-Forwarded-Encrypted: i=1; AJvYcCXlCy7nYvlR4V3OXoVO4prgkfGUSIjIuGzO6vFwSAXvKSHBUJV72G4KLdz5VVyEzEpTlimpIhocO+l/1LT3yDNHU3CT X-Gm-Message-State: AOJu0YzAi+2d2+xZtcC0dGOjJFg5D3eJhg5093dm2tZmol0V+ot1zSKq r5s3TEnGg0gK9YNoWewQK78tElaXPGx4ZrK4bex6GeMGdfreZ7UG6qpLln68vs2P8X4Vswr9dRv J7Q== X-Google-Smtp-Source: AGHT+IHmTiZP61Xdw5I4MgZP6S1EGzBAU27OWO+ECrGNgj9iXk3fTSjTU3SpedzKrf2XxG9PXneBv/rj30E= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:2291:b0:dc7:42:ecd with SMTP id dn17-20020a056902229100b00dc700420ecdmr315101ybb.6.1711677250595; Thu, 28 Mar 2024 18:54:10 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:20 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-6-drosen@google.com> Subject: [RFC PATCH v4 05/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-State: RFC 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 b413e0bfd61c..e51ec198af3f 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) /* 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, unsigned size) +static int fuse_copy_lookup(struct fuse_copy_state *cs, unsigned via_ioctl, void *val, unsigned size) { struct fuse_bpf_entry_out *fbeo = (struct fuse_bpf_entry_out *)val; struct fuse_bpf_entry *feb = container_of(fbeo, struct fuse_bpf_entry, out[0]); int num_entries = size / sizeof(*fbeo); int err; - if (size && size % sizeof(*fbeo) != 0) + if (size && (size % sizeof(*fbeo) != 0 || !via_ioctl)) return -EINVAL; if (num_entries > FUSE_BPF_MAX_ENTRIES) return -EINVAL; + err = fuse_copy_one(cs, val, size); if (err) return err; @@ -1036,7 +1037,7 @@ static int fuse_copy_lookup(struct fuse_copy_state *cs, void *val, unsigned size return err; } #else -static int fuse_copy_lookup(struct fuse_copy_state *cs, void *val, unsigned 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 *cs, 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 = 0; unsigned i; @@ -1055,7 +1056,7 @@ static int fuse_copy_args(struct fuse_copy_state *cs, unsigned numargs, if (i == numargs - 1 && argpages) err = fuse_copy_pages(cs, arg->size, zeroing); else if (i == numargs - 1 && is_lookup) - err = fuse_copy_lookup(cs, arg->value, arg->size); + err = fuse_copy_lookup(cs, via_ioct, arg->value, arg->size); else err = 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 = fuse_copy_one(cs, &req->in.h, sizeof(req->in.h)); if (!err) err = 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 -= 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); } /* @@ -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 = 0; + req->args->via_ioctl = from_ioctl; if (oh.error) err = nbytes != sizeof(oh) ? -EINVAL : 0; else @@ -1992,7 +1995,7 @@ static ssize_t fuse_dev_write(struct kiocb *iocb, struct iov_iter *from) fuse_copy_init(&cs, 0, from); - return fuse_dev_do_write(fud, &cs, iov_iter_count(from)); + return fuse_dev_do_write(fud, false, &cs, iov_iter_count(from)); } 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_inode_info *pipe, if (flags & SPLICE_F_MOVE) cs.move_pages = 1; - ret = fuse_dev_do_write(fud, &cs, len); + ret = fuse_dev_do_write(fud, false, &cs, len); pipe_lock(pipe); out_free: @@ -2286,6 +2289,33 @@ static int fuse_device_clone(struct fuse_conn *fc, struct file *new) return 0; } +// Provides an alternate means to respond to a fuse request +static int fuse_handle_ioc_response(struct fuse_dev *dev, void *buff, uint32_t size) +{ + struct fuse_copy_state cs; + struct iovec *iov = NULL; + struct iov_iter iter; + int res; + + if (size > PAGE_SIZE) + return -EINVAL; + iov = (struct iovec *) __get_free_page(GFP_KERNEL); + if (!iov) + return -ENOMEM; + + iov->iov_base = buff; + iov->iov_len = size; + + iov_iter_init(&iter, READ, iov, 1, size); + fuse_copy_init(&cs, 0, &iter); + + + res = 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) { @@ -2319,6 +2349,12 @@ static long fuse_dev_ioctl(struct file *file, unsigned int cmd, fdput(f); break; default: + if (_IOC_TYPE(cmd) == FUSE_DEV_IOC_MAGIC + && _IOC_NR(cmd) == _IOC_NR(FUSE_DEV_IOC_BPF_RESPONSE(0)) + && _IOC_DIR(cmd) == _IOC_WRITE) { + res = fuse_handle_ioc_response(fuse_get_dev(file), (void *) arg, _IOC_SIZE(cmd)); + break; + } res = -ENOTTY; break; } diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index ac61f08fd85d..dd62e78e474c 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -336,6 +336,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 0c0a9a8b5c26..74bc15e1d0b7 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -1075,6 +1075,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]) struct fuse_lseek_in { uint64_t fh; From patchwork Fri Mar 29 01:53:21 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609883 Received: from mail-yb1-f201.google.com (mail-yb1-f201.google.com [209.85.219.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 70ADE3309E for ; Fri, 29 Mar 2024 01:54:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677255; cv=none; b=bE8R4Pm6ZzWnk3loAyXqOSfTEkeI/cUvCbvbWiqAr8nT0mEPQ7DDPdXqtf53f4Z/VdJdba6nLl49k8EjJaKJ2WkDlfKQpm7wkPiCsq2/aUab4VVD/Rjf9Bc8R/W3T45dISH9veVElWx1RdDlgxxW3JkDqhDEGcu0LJm25ztQa90= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677255; c=relaxed/simple; bh=eksvIxAmmQXuXXW8c6928tLpQjkLP4OTZFUN7IvQeAw=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=U3TQ7sAEL5B8eZJnXESJetorimKGnmK5ARYr9+Iy4CVwiHv0rZVE7gCKMEmPpL70awcYcO+rwJvz/KJM8H6LAcf04hjBpA1oJfoFyGv8ZX/KRaZdXIsAz1kOWBxmQeBkz1vbFz/xIEOtdvaQIsRA6wKDRpk6NjwEni5vDhXlZkU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=izwZp+BM; arc=none smtp.client-ip=209.85.219.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="izwZp+BM" Received: by mail-yb1-f201.google.com with SMTP id 3f1490d57ef6-dcc58cddb50so2789180276.0 for ; Thu, 28 Mar 2024 18:54:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677252; x=1712282052; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=MmbRuGQ/QD3ODDkbWEuu2YWzLi7SZ6xa0DaniExgEL0=; b=izwZp+BMYISb1jG3g8WkhEkmwaGBXQcVF5KA84EDfxMQRwssSDCNH2O+QRdsW10UNn PvRZ3moPyi8KRkUdsWgMBHi5FCCGG5usvt0qIebLoxhsEqotNFamSD1y/rUbfvnSzzTD MdduiNUmO7ZLKHom7E1RRbnsd618QjpJywNm5lft8tiK/KR0DSvaR9cDvZxQEjks6wqw 67RGbNMYH8plU8rJUsM6OY65jj0Umme/lS/goQG0KCSUljM/0E3ealzhaMDGHYd0rNJo OFTrIWkKMPEHHwcqJV38LDPrQ6Xb9fgxmH/j771u/vwBUMsxRKJsaarIeEav1T1jVAXb OEWA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677252; x=1712282052; 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=MmbRuGQ/QD3ODDkbWEuu2YWzLi7SZ6xa0DaniExgEL0=; b=p7nVYO8+Hie2xP3FDXn0sM1UL37xO3+CERSqwRotdxebtSV+emtkWIXO6qZLo6pm7U XLu4tdUK+lYRtfcy6hiSOro1p0oT/dvv+xxv3o/LgF+Z7VkOX+SlrVr00cdTpjW2k0z6 C6Gcx3QWFpxFjH152Iba+vSgtvjqzrBOD9vRews6EL5VeU1NwEu2ykJR+PugDqBn7KrK KwbYV1TQeABOM7VQAEzXVjgysGm4aFIDzDB/FobI3LpRyLeh0jdZ8V8pNOLafU/R4mk1 t7rlKk8z5RvlqSoIKa7K/R+XhezHqPNhNoKHA4cx5eFeIuPMC5/5zSfSDUAEQ1EpKnHq Rw9w== X-Forwarded-Encrypted: i=1; AJvYcCXmKrC0uL13AJUQsTS001nD9Tuqc63e0b8MpiG3ciFwcheitzg7O5SNRCZUtqnKQW8FHzw9/g1LQcHKJ0pHkSpLvmn+ X-Gm-Message-State: AOJu0Yx4EI7eMZ6oiw+IiZvgVzdXQQ0GbvlVP1Mxx9hP0/xNbXGQvDRb jw4S2QnqY0HJvYoImaYyk7M4YPJtFghDzHtZRQN3QBOmMCZsEThqW8RjqayublJpEpFB7HRvkYJ EJw== X-Google-Smtp-Source: AGHT+IHTtbLuJY0/Xd21xc4JGUtsmkGoiiTVX+jnYhPEA0lXMA0moNx8uO5mPUiTFXzzmc0XQmcHJ+pEMF4= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:1b84:b0:dcc:e1a6:aca9 with SMTP id ei4-20020a0569021b8400b00dcce1a6aca9mr294907ybb.9.1711677252599; Thu, 28 Mar 2024 18:54:12 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:21 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-7-drosen@google.com> Subject: [RFC PATCH v4 06/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-State: RFC 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 6570fe7a9b53..b47b2e41e5e4 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1187,6 +1187,14 @@ static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len, nodeid = get_fuse_inode(inode)->nodeid; generation = inode->i_generation; +#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 = 0; + return FILEID_INVALID; + } +#endif + fh[0] = (u32)(nodeid >> 32); fh[1] = (u32)(nodeid & 0xffffffff); fh[2] = generation; From patchwork Fri Mar 29 01:53:22 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609884 Received: from mail-yw1-f202.google.com (mail-yw1-f202.google.com [209.85.128.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A997C39AFD for ; Fri, 29 Mar 2024 01:54:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677258; cv=none; b=tSRPM9qkg9V0/Jns9U+7FjxvCg2kwTIBDm6LMpW4IQwVDI0vCrOiy2ey8wrv4+3G23opGAyDQZPSYkM8bVp+SZNn5cP1xsR9ZFwOWq044tkbbOJke458xaqjRI/vKJJG3AIBXJo4E8iAIA64U0foHsJYYNSZsh+4DRVcb3Iz20I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677258; c=relaxed/simple; bh=tFmf24Mjc0l+J1FvLtlzPLbHQwNkOVITHodym4M2p8g=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=WObKOj1Hw4oCDFo8Z0H4v/LKhCJ6HuMqKdwHHHhDvh8TSq228T2zgGILg4Fj55rTGNlvU8hRIjuh/gFNi7maVgvC+Kq5hRx3FiVY5pHi3R/tO7033fZ5xFa6I/Bp5Hvrg6zPcSV4qLnZqfCGln/NYNMC2t+N+a8tiwlMdboRruI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=YmargXJs; arc=none smtp.client-ip=209.85.128.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="YmargXJs" Received: by mail-yw1-f202.google.com with SMTP id 00721157ae682-60ff1816749so27306067b3.3 for ; Thu, 28 Mar 2024 18:54:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677255; x=1712282055; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=eeuNGA+8CuovI+htgf5CrcA9lpm9o2FtGFCkGxKFo9c=; b=YmargXJsKh02pEkO2JoheAf/ikD8SSfSQ51Blbp5F8DAr5dPQ/rnMKP/5o8iF8+jRv 7mFRutAXe0FIFDAAWby/UVsz8XTcP8AjpQ1LOwhtLzjPdinT+jauAaBhUymSJOZCvMqb 1FNIFM2djvTlX3jj3gGgSa9fEPCY0SOrTP9MwQNmkjmmhOqmIqFE4huYbJcpZIy/AAbX 9FzZUvtp61PhBrXzdY1GPSDadvIX1nht7opJRX0OvTXnhhdTCX98ZJvI8MWkL1VK3fTz xzLBQzXRYxFaR8Yp+Kti9kNO2amKVzfC13R7/c/a1EWwZfJzQS8S9SrE7QekU068oUGm RRww== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677255; x=1712282055; 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=eeuNGA+8CuovI+htgf5CrcA9lpm9o2FtGFCkGxKFo9c=; b=WIBQtuljCE9dzaaGx/m53nrxYMsvYkhP7DpkDnsV7p7O1mmqO3VTlSHijjfXuyxmu2 5xR7QS3fknfB/E+7OJbrAMQZBfuG6i5UTz7epMbnPg8XYLSfDsp1JBxS06GOhxXjXRxL HWVtArk/GvlI3RUc1dr/LppgsGKWlhO/bY4EhpEAI26cFyZ4nhT1BslP2fkCMoKyus02 ZNW5n1LamIYDFDaLzxPKOhqSv+zzpuYzGdL5XXfb2FceqH7X+EZt9Fvk5/zJM4Q5WwR2 lPacC9of8h4Qm2LxllALJB4te2CCOozeOZuhacz5OTH2qPUFeb1bPnWgRF1dB7zayVm+ yCzA== X-Forwarded-Encrypted: i=1; AJvYcCV7iwK8S9ObCO86mbTN4dIi+8uGEfiQOUR3l74tzrvlAy3LR7f/LriUzTaI9G8ywFXZmo0aIAtgicp0Mi76KUZEZt04 X-Gm-Message-State: AOJu0YxXaHvpWiu6TnBRAxZHjcwoxsxR2cTnaYowrA3pYpvd/Ap1gJgz +mYxJbnZJYNrxWqaMS1WQUeaA0tkZtOJauzDZBQIf+koGm6ZVOv9Fs4tL6TM+dctW598CbZ+XRq 5lw== X-Google-Smtp-Source: AGHT+IHRQU6ab0eRho+UjDvwkzeb9d7pS8VugdyuU3acPa0I9eBDEaOKo5pla1Pw5XzmlyStLuGkngRj8h4= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a81:4e10:0:b0:611:5ca6:59c3 with SMTP id c16-20020a814e10000000b006115ca659c3mr248817ywb.6.1711677254863; Thu, 28 Mar 2024 18:54:14 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:22 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-8-drosen@google.com> Subject: [RFC PATCH v4 07/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC 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 14fcc2032764..a94d99ff9862 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -420,3 +420,50 @@ int fuse_revalidate_backing(struct dentry *entry, unsigned 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 = (struct fuse_access_in) { + .mask = mask, + }; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .opcode = FUSE_ACCESS, + .nodeid = get_node_id(inode), + }, + .in_numargs = 1, + .in_args[0].size = sizeof(*in), + .in_args[0].value = in, + }; + + return 0; +} + +static int fuse_access_initialize_out(struct bpf_fuse_args *fa, struct fuse_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 = get_fuse_inode(inode); + const struct fuse_access_in *fai = fa->in_args[0].value; + + *out = 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 6503c91886f6..8db6eb6a0848 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1563,6 +1563,9 @@ static int fuse_access(struct inode *inode, int mask) struct fuse_access_in inarg; int err; + if (fuse_bpf_access(&err, inode, mask)) + return err; + BUG_ON(mask & MAY_NOT_BLOCK); if (fm->fc->no_access) @@ -1619,6 +1622,9 @@ static int fuse_permission(struct mnt_idmap *idmap, if (!fuse_allow_current_process(fc)) return -EACCES; + 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 dd62e78e474c..08ee98b7bb95 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1439,6 +1439,7 @@ 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); +int fuse_bpf_access(int *out, struct inode *inode, int mask); #else @@ -1447,6 +1448,11 @@ static inline int fuse_bpf_lookup(struct dentry **out, struct inode *dir, struct return 0; } +static inline int fuse_bpf_access(int *out, struct inode *inode, int mask) +{ + return 0; +} + #endif // CONFIG_FUSE_BPF int fuse_handle_backing(struct fuse_bpf_entry *feb, struct path *backing_path); From patchwork Fri Mar 29 01:53:23 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609885 Received: from mail-yw1-f201.google.com (mail-yw1-f201.google.com [209.85.128.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E8D663B290 for ; Fri, 29 Mar 2024 01:54:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677259; cv=none; b=sZNAhDLYbG0GrkDaxcPbBfrygDccieC57cJT7We5rzJVCVuktc/Ljb5mVWuP7kWpXrofIBG+ZpwAB71Ny+1qBzRHA3FxyvaKrD7DsMHPXZDUx/1vp2FcX/4SS70FQfkeM8zx0u4ULFROOrDE+Hdav/PB6MML0cOb3ValQvDwzMs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677259; c=relaxed/simple; bh=v5AwnjQKV/7OH7UGGRZtC3sy6l1pNzMEDPgRfXAhH7g=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=HJl9488/ls6e8E4JGjSEYqocOY4LQqF0mJEQRLTCD3xY9wCEzo8suTZa8KslITEz9Z9JodUr5+SPEfZlY4/kx84nvvCXlEfDaVUnD0LtcDywZ7VctJfTtNAsQJjAGTLvv0f90SNZr4mmIlhXIup62imi6E73DAU1uwFiJ07EpDw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=imRu2q3Z; arc=none smtp.client-ip=209.85.128.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="imRu2q3Z" Received: by mail-yw1-f201.google.com with SMTP id 00721157ae682-6144244c60cso2355737b3.2 for ; Thu, 28 Mar 2024 18:54:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677257; x=1712282057; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=1qqFZWrpXHSloU4DHzngdNGiVBp7dRUntmvVSQg8HHI=; b=imRu2q3ZUgP4igk/dyN9A0ZpjyHHhMbidocZhV7Q9uzufiZOawkgJjQyO0OMwzyq5u D+taXfRplr6J0gESe53K23uhR1J9xFTMLRsDuP5TCj+qwVG0Ynt7RsUsoanoddS0Ybng 2TR4Yt9CWGPhrDwZpDq/DOY4jZTKZyYio+6xF9myWMZ8I2sTiy3qP5wbPGvInFyHUSpG oiTicVQ/LnCeOwSgWZr8UA9ABwT1sMkeEJQek6KJlOoCqdPrsxnfCw2RdMHW8Ozq9Kd3 GQaJXeCaOKB+/2TJqH5UoHeaNMLJIqTwZOS2DMvtOYIRejWbl2WqbQBkEAO48FDvsiZo qinA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677257; x=1712282057; 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=1qqFZWrpXHSloU4DHzngdNGiVBp7dRUntmvVSQg8HHI=; b=bT0DuMt6Mp8naWhTDNdq0gaCrQ7kIPAB1neiX6mNppfM3dydoYt4JB1xenOaH1V5sX 5b0BQURiK9+tyPRkDYH8sZuVpzi9YT6y0WzCuu2rHfO0yc8bZHXXWhAJ9NBERMghRdO0 4t87fhwVg7a8cDLh+VaRhkdKfTaIQHI+tdexJz64aJPbVUp0I3Rndg3i+x/t6dMsCaYb E7rvrLzx5vXL+9vbOqWtJQhnzzgk+JpH9380Bcha2vol1NcimuBNIZLGAjFYHePFqKwv /KYF6z6vXaXdvY065ilhW3sDA0hznut+r3hvkhuIZo2WSwFyrnqNslf+xyyiDuZ28/zu qtsQ== X-Forwarded-Encrypted: i=1; AJvYcCVe7DKoUoN3VJPYLDUr6RjBesCV3dd5/nTqkYLc+iXdQsaOxkrB6hfV7/RKx3smzmXKzGFDeO8HXdrHobwSmTRNCrOT X-Gm-Message-State: AOJu0YwOa7l9X8DmOkeVHtI6l9z0SlDnoCBCaNjAGsNiVRPsd9t0Avi3 WWBxV3V9jgiVaqj/4WPlwjp2QfDnDTxbzH6tEb0GJ8F3SHLMn3JAY669yCCihJ5v5QXG4QJ6g8o IPQ== X-Google-Smtp-Source: AGHT+IEqoqcj1yZ2rfja/2VzBLgaTmhWJV22bhweKMkMmlc4g7KXcHe2vHz2DKsC848nZSKQHKYRB2huEss= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a0d:d816:0:b0:611:a254:8b9 with SMTP id a22-20020a0dd816000000b00611a25408b9mr254675ywe.0.1711677257071; Thu, 28 Mar 2024 18:54:17 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:23 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-9-drosen@google.com> Subject: [RFC PATCH v4 08/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC 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 | 41 +++++++++++++++++++++++++++++++++++++++++ fs/fuse/file.c | 6 ++++++ fs/fuse/fuse_i.h | 4 +++- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index a94d99ff9862..331b3f23ef78 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -210,6 +210,47 @@ static void fuse_stat_to_attr(struct fuse_conn *fc, struct inode *inode, attr->blksize = 1 << blkbits; } +ssize_t fuse_backing_mmap(struct file *file, struct vm_area_struct *vma) +{ + int ret; + struct fuse_file *ff = file->private_data; + struct inode *fuse_inode = file_inode(file); + struct file *backing_file = ff->backing_file; + struct inode *backing_inode = file_inode(backing_file); + struct timespec64 mtime, bmtime, ctime, bctime; + + if (!backing_file->f_op->mmap) + return -ENODEV; + + if (WARN_ON(file != vma->vm_file)) + return -EIO; + + vma->vm_file = get_file(backing_file); + + ret = call_mmap(vma->vm_file, vma); + + if (ret) + fput(backing_file); + else + fput(file); + + if (file->f_flags & O_NOATIME) + return ret; + + mtime = inode_get_mtime(fuse_inode); + bmtime = inode_get_mtime(backing_inode); + ctime = inode_get_ctime(fuse_inode); + bctime = inode_get_ctime(backing_inode); + if ((!timespec64_equal(&mtime, &bmtime) || + !timespec64_equal(&ctime, &bctime))) { + inode_set_mtime_to_ts(fuse_inode, bmtime); + inode_set_ctime_to_ts(fuse_inode, bctime); + } + touch_atime(&file->f_path); + + return ret; +} + /******************************************************************************* * Directory operations after here * ******************************************************************************/ diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 41a2e88e8646..b15a0c30fec8 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -2486,6 +2486,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); +#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 FUSE_DIRECT_IO_ALLOW_MMAP isn't set. diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 08ee98b7bb95..5f76ba3c1e4b 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1455,7 +1455,9 @@ static inline int fuse_bpf_access(int *out, struct inode *inode, int mask) #endif // CONFIG_FUSE_BPF -int fuse_handle_backing(struct fuse_bpf_entry *feb, struct path *backing_path); +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_path); int fuse_revalidate_backing(struct dentry *entry, unsigned int flags); From patchwork Fri Mar 29 01:53:24 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609886 Received: from mail-yb1-f202.google.com (mail-yb1-f202.google.com [209.85.219.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0B290A927 for ; Fri, 29 Mar 2024 01:54:19 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677261; cv=none; b=sTY+Aml1HNKtT670XviR1MvosMraRil9WO6dV5GgPGuT9+AuM6JriuL0dBl816kZTQvJk9QgQ9UOgLofMBxnDKOxaHs0H0OMoPYIH3g545l5inWHuCb4Xc5qedkTF5GR4gCWaDyT3VPSZnmXLNd0qjL6+hwlwwgwqQtLu8T3++4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677261; c=relaxed/simple; bh=RyIro3EkoiY+3HAsIMRPzlL5mpldX3bcrAieK2dC6yw=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=gdgjE4YOzft07gZqr2qy/4Er5FSsVIXPG+yL23uDK8lhyf00hhw4KgGjPxCzR8qlI2pbmFrE6m+Ksyg+LO15PBs6cZ7XG27Q1cGquAFsmHByiONYJlfKTLnzaXZZKMXmjpxwoUBq2YQACfJGXWG5qnAyKFCAtjN4yNk649m089c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=i64Bjem3; arc=none smtp.client-ip=209.85.219.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="i64Bjem3" Received: by mail-yb1-f202.google.com with SMTP id 3f1490d57ef6-dc6b26ce0bbso2760940276.1 for ; Thu, 28 Mar 2024 18:54:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677259; x=1712282059; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=4H9jwrEyVjW9lQYUifpGpfprHrYEWKE7Hq9AeQThRu4=; b=i64Bjem3CrihJB5MnEEFDFEKjBnVT6kPurADrx8f43QYYKq/YiQFIdj1mmDh/0y9gx ZeM8bnDDopH1gW58sVeb7oD8pFwOSxZ3j0MZpNe+r2YH+y9shqGzlzeX/8UJt87CQ2nH i7Qta8Np3iarl+wZ+nYFmY0e5jUHyHyd1W0CUMwcZD7o1JmfghCpeBdoWIjLh3vovi3Z CgEN0VyYcMEDFFcTFZPLVvAKqHan62U4c5eeeLW7iy1I02JrUK9i2bkNXjAitlZkrSW7 iLAgZ+Bvu0ZNXZsu7ZaO1uCl/gs2qELUyW6LpUbwuNIxLU0N1DMNMCwrviLIv835Kn5j 1pzw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677259; x=1712282059; 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=4H9jwrEyVjW9lQYUifpGpfprHrYEWKE7Hq9AeQThRu4=; b=s6QXgZlJKxyA3KldlsVHKpq84Ybaq8LuEKUzqpW4xeRRHvEGj7LJPoWMyrfa09aWEi UDm6dONCogEK5dM5RmRsSAnKLeZgKwbW6R+3OuDtCKynS7xw1S/7vNIBhw/tw38vx/4O J+jRhOBL8kI/hSSccpQjV/IXdsGmPYdn83evdy0tkrZkkDObeEoxV7GzJZz/OAEjG8A3 xj9RygYbVGB1+Z8LlRz7U9o2OEB1w70zxhqS6ZCn/Jls2GgCQOl3dcs34uugb/qeuqSJ g1t7NLE8YxKCX9o7n8TciH8JUzy1/NGx/ZDwmP+U4EUquoRQ6JcZmrkULcyz/p1Yo31d wk2Q== X-Forwarded-Encrypted: i=1; AJvYcCX/MVo2Xk2Q0hN0eB2RkKqvROMISHJ5sYnu0S9Bt1WtLvK4Taf9xQz5FcZhYyRrmOEp5wPQDvxq39B6wtEhWeoKLMIv X-Gm-Message-State: AOJu0YwL04wFaX5blqdBd6lXfkmVBoOBqP+g/SW85UdsI2rfbzm5VNdF Np+ayahxmoSVNbebTGXsWvuBVFt1XDFYFTIRB/BGaKGqycUcadhX+UkPl09p9GP+jnONPrNL31J gAg== X-Google-Smtp-Source: AGHT+IHPJEbSa2A0quAGCXxs0y5QrngqO3/MgdqYF7v/sQ3XbarIyz8QwwSXUNDKGtp9jqGgm8cj8LL2bTA= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:218b:b0:dcd:875:4c40 with SMTP id dl11-20020a056902218b00b00dcd08754c40mr305761ybb.10.1711677259230; Thu, 28 Mar 2024 18:54:19 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:24 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-10-drosen@google.com> Subject: [RFC PATCH v4 09/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC 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 331b3f23ef78..04cb0c0c10b0 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -210,6 +210,95 @@ static void fuse_stat_to_attr(struct fuse_conn *fc, struct inode *inode, attr->blksize = 1 << blkbits; } +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 = file->private_data; + + args->in = (struct fuse_lseek_in) { + .fh = fuse_file->fh, + .offset = offset, + .whence = whence, + }; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_node_id(file->f_inode), + .opcode = FUSE_LSEEK, + }, + .in_numargs = 1, + .in_args[0].size = sizeof(args->in), + .in_args[0].value = &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 = 1; + fa->out_args[0].size = sizeof(args->out); + fa->out_args[0].value = &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 = fa->in_args[0].value; + struct fuse_lseek_out *flo = fa->out_args[0].value; + struct fuse_file *fuse_file = file->private_data; + struct file *backing_file = fuse_file->backing_file; + + /* TODO: Handle changing of the file handle */ + if (offset == 0) { + if (whence == SEEK_CUR) { + flo->offset = file->f_pos; + *out = flo->offset; + return 0; + } + + if (whence == SEEK_SET) { + flo->offset = vfs_setpos(file, 0, 0); + *out = flo->offset; + return 0; + } + } + + inode_lock(file->f_inode); + backing_file->f_pos = file->f_pos; + *out = vfs_llseek(backing_file, fli->offset, fli->whence); + flo->offset = *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 = fa->out_args[0].value; + + if (!fa->info.error_in) + file->f_pos = flo->offset; + *out = flo->offset; + return 0; +} + +int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_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 b15a0c30fec8..b3cae6e677f8 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -2740,6 +2740,9 @@ static loff_t fuse_file_llseek(struct file *file, loff_t offset, int whence) loff_t retval; struct inode *inode = file_inode(file); + 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 5f76ba3c1e4b..24f453d162ae 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1438,11 +1438,17 @@ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe, int num_entries); #ifdef CONFIG_FUSE_BPF +int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_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); #else +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; From patchwork Fri Mar 29 01:53:25 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609887 Received: from mail-yw1-f201.google.com (mail-yw1-f201.google.com [209.85.128.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9D26B3D980 for ; Fri, 29 Mar 2024 01:54:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677264; cv=none; b=O7H0QLbTR7KKrE7BpM6lyubBq0gH3mnSdGVhe2szib1J15q6ppsm3SJ6hnZoD8ecy13FgQLljjMuSWXyFIl9IXZRnb8WSj9dWNLKBkTVBsbidzOWZ8Dq8l9pJ+PvOyFstEPpMl591xIZYfBkPxiTeVenDRyyFWsASVin19YjtrM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677264; c=relaxed/simple; bh=fvczagys3eeKlVoeCdtC8aX8ILYO2cwo2aEBbr60QNc=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=n3K/8X065YMw3B2HxHqmATSg5LHi+9kQi/HlLN4ES5f9HulNVgvgzAXCKRkRwa/x4S1/IxVLr2Z6BJOhIwzFxcB5p1x1vlVXj2CWIejwBvla+7RhFYPWi5JL+Erz093axriOzP+iVooW4PciroQt/4V/iEg9TrsasChRKT5jX+8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=4Q3cKsRZ; arc=none smtp.client-ip=209.85.128.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="4Q3cKsRZ" Received: by mail-yw1-f201.google.com with SMTP id 00721157ae682-60cd62fa20fso30387797b3.3 for ; Thu, 28 Mar 2024 18:54:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677261; x=1712282061; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=RN40RVFAgvwoqdUJunI82ty/5fzSZehhGVy3k+SOK50=; b=4Q3cKsRZW9AkZtU5EdHpgZ4ObNUKH7d0fjZr4czVLSa+vVY4e1BSdqntdLokO+T+J8 JA+ZUDp7/qUU3vIXH1IuzUODnp7PFyJtO/0l2tTaiGfQj+OoI6jdal8qNTHlJr4zkspv 4reQV6kDN4d9cqy9kBzna0vT5Vn6lZIjCyckVQfUmuOEPdnCp6iG8FTMjDyFRdlPIaTJ GPbJUn0KsetvsfLFKO2v42y45YAQqCq+icSSsVOBh0AmPBSwQXbevSTVGA+rfZggFTHM ZElBdktoNJpCJqauTSiYeaHtR6ATUp9/gziSi9fbUgnWWDoBkOUEyiQnQaHDEo712m5B eLug== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677261; x=1712282061; 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=RN40RVFAgvwoqdUJunI82ty/5fzSZehhGVy3k+SOK50=; b=IPnbxm6wHxz5626Hm+QRw4ju4yhnYh5csDK0wIdfn/VipIy7/soGDYxwB3DXVRoEcT /PWoGDxXKoFCoTfQwo8L3rGq8RWc7nMblJG88CO1YtKJV5nVqY8oK3sle465b0ZG7TXb W20qD5LqryDS5ZJawHSiCFejDqjOcObK0SlxhDy0MAw267xlHfFiYVsiVdHM3BffGSxy UqqH7sPqrI/hd/xlozbYPjW94Bfx1GjilYXlloLwH3Q4jsZSjt15vMax1KT3qJ8Zkkmx H1Etd+DJCKwtLO+HFN8kyjKcT08gXpyPCc8Lnvlc9/uNRMjT0hiNNFsD1/ZmH8fKY3nM 78rg== X-Forwarded-Encrypted: i=1; AJvYcCVwQHg0OIjg7cYocgKyJVLCLdpQPiDKoYZcEvVpcZO5V4hLPf0xPRSpRdYYYWbJXHHwnZxfKnPbRF9YiJuRrW1oFD1a X-Gm-Message-State: AOJu0YzD88FUQj7qNU9Uux5gxC8pK9A+7qWV5twsJQqoh2BAAPlSCAkN 9cKlGTxUYZKEPhZuefScHVk1oxPYf0GWy6iH4QFjRMmF2czJhuZs2bjMjg8dlFxR7pWQFEPt6rI 3Kg== X-Google-Smtp-Source: AGHT+IGuOQt8Snd/P6ofj2gX/JcTFHrD/nXa6QfIFbIPo49OngK3DoVwSGK+2/UzTDKaYsETWaFnfy3xB1Q= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a0d:efc5:0:b0:614:fa:c912 with SMTP id y188-20020a0defc5000000b0061400fac912mr269078ywe.1.1711677261512; Thu, 28 Mar 2024 18:54:21 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:25 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-11-drosen@google.com> Subject: [RFC PATCH v4 10/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC 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 04cb0c0c10b0..4a22465ecdef 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -340,6 +340,66 @@ ssize_t fuse_backing_mmap(struct file *file, struct vm_area_struct *vma) return ret; } +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 = file->private_data; + + *in = (struct fuse_fallocate_in) { + .fh = ff->fh, + .offset = offset, + .length = length, + .mode = mode, + }; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .opcode = FUSE_FALLOCATE, + .nodeid = ff->nodeid, + }, + .in_numargs = 1, + .in_args[0].size = sizeof(*in), + .in_args[0].value = 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 = fa->in_args[0].value; + struct fuse_file *ff = file->private_data; + + *out = 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 *file, 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 b3cae6e677f8..0ab882e1236a 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -3032,6 +3032,9 @@ static long fuse_file_fallocate(struct file *file, int mode, loff_t offset, (!(mode & FALLOC_FL_KEEP_SIZE) || (mode & (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_ZERO_RANGE))); + 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 24f453d162ae..7a6cebecd00f 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1439,6 +1439,7 @@ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe, int num_entries); #ifdef CONFIG_FUSE_BPF int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_t offset, int whence); +int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *file, 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); @@ -1449,6 +1450,11 @@ static inline int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file * return 0; } +static inline int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct 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; From patchwork Fri Mar 29 01:53:26 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609888 Received: from mail-yw1-f202.google.com (mail-yw1-f202.google.com [209.85.128.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 688BC3FB90 for ; Fri, 29 Mar 2024 01:54:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677266; cv=none; b=By8YfGiPlzdkiuQ3SiI92ydqXUvVzUuJvyo5KlGPwBQR7xH5V02ky5MSlKyYD+UJ8gst8GMBlVI1FQz5PUOa1WmVsFMQSBKf8WS6k3210jd8aJ/YlAd94iw4YNFHIG9yYjq1DHNms9/JY+XR5E52A9l7yvGDTw4z4M0wSUZQFos= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677266; c=relaxed/simple; bh=Fkk92+rnt1CGnhyzsoVnRC9lfBnZyOPhYO5QkWWNTvQ=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=fJZEtCvpj5Q5+W64mgLWndS/WYOrMVoaOSDvvIqmAVxEaW9f9nFQ95BzLoBp2z7q6Sv9dY7neY3oBV3d1kCLYKvM2gssGj/pTNFOWAsiV/S+MgX4exGVYNXo8Yw+kkqoDfUWD8GXKxCb2yckmtswlidaDMSGSJHoY09yXCc9jks= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=IjaSbV0p; arc=none smtp.client-ip=209.85.128.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="IjaSbV0p" Received: by mail-yw1-f202.google.com with SMTP id 00721157ae682-60cd073522cso26412607b3.1 for ; Thu, 28 Mar 2024 18:54:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677263; x=1712282063; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=MrUtVK6+k7+1QZ3C4cNIecBhDWMks4vBEWUta1bw0HE=; b=IjaSbV0pq+ZhIw2JDU9172KAypV2z/sAra6GyIXNHfPWa6tVO4zVGBqjrbOBbKPFSJ NRW0P9HACSxAwJbXWDFWirAtHpO80z7DRd8i/xYssYH1cINthA84h7Cq2dte/Oh2VTu0 5nrki2MngvFok2xGhao6GGN/nmLqvylB/AXRcSBJGYmnD7ofI20RcWR+pgvRMYqvWyGz zj//82gNuLlrf8M5r9rJb9dwXnBH9Vz4TEBTN2UmwWURCcNHKLIc6k2NxHqDKjKGSkiD IYz8CqtACNItAXKUGYH7AKJ71FyKfMCKpXQqmkCW+H2Tlo4FvwtZiOUYasyelJmIHRpB cZog== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677263; x=1712282063; 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=MrUtVK6+k7+1QZ3C4cNIecBhDWMks4vBEWUta1bw0HE=; b=WVH5dd76zFGoXWrTV6GsLXeYYCHflPd4Ww8lzAokNC/PFsd/00Gq3yd8KbG61gfV+9 EXiyo6Jur6OlCrumfuNDkIwo05W4Z6tXZF1UXQ84er3etzVH3qXzC3py9wRK3ieQl5p+ fRxM/VjfZ9MDOQw0Z/ru58PW4KhNUILWEipf8NjDPVXUFGW8GbLFLoH+70XEiH4iXlFV 74xaN3CWJH4r8iNKkM6+nyS8IY76kHJmkxCta4WVJcfQG+gMROuKnpDzi3HzBut57ed3 cM/NAh22MkvXPoCDOVKw+6pAjP+48UMEayNuGTEPsJdp9yk+VOl4SowIaJYDTSB0FJcr Ds6g== X-Forwarded-Encrypted: i=1; AJvYcCVoOJ/KsV0oQUavsKl9SShy93hfZEYP6FXKvSxZ5ZJ6GSu0zyhijY+TncysiyiWb229zR2xvK2UE0tGliHsITMuUZvI X-Gm-Message-State: AOJu0YyPf+Z7VS8ZWxfY+fIg0u4N5SQKZE53eBqZziE8rKx0RbWFBlcV CE1duPv3l+I1cuY8v6gsGAVbJUz78LeMFyc5Hc8ohO3XSmklV3YbFwG7CmKv5jJMJ3eBcvOf4ko pEw== X-Google-Smtp-Source: AGHT+IHUlRTQe/24H5Rd3XUF8D0L0jK5ORat1YiNu2Hnuiw60ohY9AVaFKP7PVzv+457HzOJe+gS/LwEsZk= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a81:f910:0:b0:611:7166:1a4d with SMTP id x16-20020a81f910000000b0061171661a4dmr302696ywm.3.1711677263590; Thu, 28 Mar 2024 18:54:23 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:26 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-12-drosen@google.com> Subject: [RFC PATCH v4 11/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC 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 | 336 ++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dir.c | 3 + fs/fuse/file.c | 62 +++++---- fs/fuse/fuse_i.h | 26 ++++ 4 files changed, 404 insertions(+), 23 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 4a22465ecdef..317a3adbbb3e 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -210,6 +210,342 @@ static void fuse_stat_to_attr(struct fuse_conn *fc, struct inode *inode, attr->blksize = 1 << blkbits; } +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_open_args *args, + struct inode *inode, struct file *file, bool isdir) +{ + args->in = (struct fuse_open_in) { + .flags = file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY), + }; + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_fuse_inode(inode)->nodeid, + .opcode = isdir ? FUSE_OPENDIR : FUSE_OPEN, + }, + .in_numargs = 1, + .in_args[0] = (struct bpf_fuse_arg) { + .size = sizeof(args->in), + .value = &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 = (struct fuse_open_out) { 0 }; + + fa->out_numargs = 1; + fa->out_args[0] = (struct bpf_fuse_arg) { + .size = sizeof(args->out), + .value = &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 = get_fuse_mount(inode); + const struct fuse_open_in *foi = fa->in_args[0].value; + struct fuse_file *ff; + int mask; + struct fuse_dentry *fd = get_fuse_dentry(file->f_path.dentry); + struct file *backing_file; + + ff = fuse_file_alloc(fm); + if (!ff) + return -ENOMEM; + file->private_data = ff; + + switch (foi->flags & O_ACCMODE) { + case O_RDONLY: + mask = MAY_READ; + break; + + case O_WRONLY: + mask = MAY_WRITE; + break; + + case O_RDWR: + mask = MAY_READ | MAY_WRITE; + break; + + default: + return -EINVAL; + } + + *out = inode_permission(&nop_mnt_idmap, + get_fuse_inode(inode)->backing_inode, mask); + if (*out) + return *out; + + backing_file = + dentry_open(&fd->backing_path, foi->flags, current_cred()); + + if (IS_ERR(backing_file)) { + fuse_file_free(ff); + file->private_data = NULL; + return PTR_ERR(backing_file); + } + ff->backing_file = backing_file; + + *out = 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 = file->private_data; + struct fuse_open_out *foo = fa->out_args[0].value; + + if (ff) { + ff->fh = foo->fh; + ff->nodeid = get_fuse_inode(inode)->nodeid; + } + return 0; +} + +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_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 = (struct fuse_create_in) { + .flags = file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY), + .mode = mode, + }; + + args->name = (struct fuse_buffer) { + .data = (void *) entry->d_name.name, + .size = entry->d_name.len + 1, + .flags = BPF_FUSE_IMMUTABLE, + }; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_node_id(dir), + .opcode = FUSE_CREATE, + }, + .in_numargs = 2, + .in_args[0] = (struct bpf_fuse_arg) { + .size = sizeof(args->in), + .value = &args->in, + }, + .in_args[1] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = &args->name, + }, + }; + + return 0; +} + +static int fuse_create_open_initialize_out(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->entry_out = (struct fuse_entry_out) { 0 }; + args->open_out = (struct fuse_open_out) { 0 }; + + fa->out_numargs = 2; + fa->out_args[0] = (struct bpf_fuse_arg) { + .size = sizeof(args->entry_out), + .value = &args->entry_out, + }; + fa->out_args[1] = (struct bpf_fuse_arg) { + .size = sizeof(args->open_out), + .value = &args->open_out, + }; + + return 0; +} + +static int fuse_open_file_backing(struct inode *inode, struct file *file) +{ + struct fuse_mount *fm = get_fuse_mount(inode); + struct dentry *entry = file->f_path.dentry; + struct fuse_dentry *fuse_dentry = get_fuse_dentry(entry); + struct fuse_file *fuse_file; + struct file *backing_file; + + fuse_file = fuse_file_alloc(fm); + if (!fuse_file) + return -ENOMEM; + file->private_data = fuse_file; + + backing_file = dentry_open(&fuse_dentry->backing_path, file->f_flags, + current_cred()); + if (IS_ERR(backing_file)) { + fuse_file_free(fuse_file); + file->private_data = NULL; + return PTR_ERR(backing_file); + } + fuse_file->backing_file = 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 = get_fuse_inode(dir); + struct path backing_path; + struct inode *inode = NULL; + struct dentry *backing_parent; + struct dentry *newent; + const struct fuse_create_in *fci = 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 = -EIO; + goto out; + } + + backing_parent = dget_parent(backing_path.dentry); + inode_lock_nested(dir_fuse_inode->backing_inode, I_MUTEX_PARENT); + *out = 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 = fuse_iget_backing(dir->i_sb, 0, backing_path.dentry->d_inode); + if (!inode) { + *out = -EIO; + goto out; + } + + newent = d_splice_alias(inode, entry); + if (IS_ERR(newent)) { + *out = PTR_ERR(newent); + goto out; + } + + inode = NULL; + entry = newent ? newent : entry; + *out = finish_open(file, entry, fuse_open_file_backing); + +out: + iput(inode); + 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 = file->private_data; + struct fuse_inode *fi = get_fuse_inode(file->f_inode); + struct fuse_entry_out *feo = fa->out_args[0].value; + struct fuse_open_out *foo = fa->out_args[1].value; + + if (fi) + fi->nodeid = feo->nodeid; + if (ff) + ff->fh = 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 fuse_release_in *fri, + struct inode *inode, struct fuse_file *ff) +{ + /* Always put backing file whatever bpf/userspace says */ + fput(ff->backing_file); + + *fri = (struct fuse_release_in) { + .fh = ff->fh, + }; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_fuse_inode(inode)->nodeid, + .opcode = S_ISDIR(inode->i_mode) ? FUSE_RELEASEDIR + : FUSE_RELEASE, + }, .in_numargs = 1, + .in_args[0].size = sizeof(*fri), + .in_args[0].value = fri, + }; + + return 0; +} + +static int fuse_release_initialize_out(struct bpf_fuse_args *fa, struct fuse_release_in *fri, + struct inode *inode, struct fuse_file *ff) +{ + return 0; +} + +static int fuse_release_backing(struct bpf_fuse_args *fa, int *out, + struct inode *inode, struct fuse_file *ff) +{ + return 0; +} + +static int fuse_release_finalize(struct bpf_fuse_args *fa, int *out, + struct inode *inode, struct fuse_file *ff) +{ + *out = 0; + return 0; +} + +int fuse_bpf_release(int *out, struct inode *inode, struct fuse_file *ff) +{ + 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, ff); +} + +int fuse_bpf_releasedir(int *out, struct inode *inode, struct fuse_file *ff) +{ + 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, ff); +} + 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 8db6eb6a0848..09bb4c63fd71 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -736,6 +736,9 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry, /* Userspace expects S_IFREG in create mode */ BUG_ON((mode & S_IFMT) != S_IFREG); + if (fuse_bpf_create_open(&err, dir, entry, file, flags, mode)) + return err; + forget = fuse_alloc_forget(); err = -ENOMEM; if (!forget) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 0ab882e1236a..c43f2d61c41a 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -106,25 +106,35 @@ static void fuse_release_end(struct fuse_mount *fm, struct fuse_args *args, kfree(ra); } -static void fuse_file_put(struct fuse_file *ff, bool sync, bool isdir) +static void fuse_file_put(struct inode *inode, struct fuse_file *ff, + bool sync, bool isdir) { - if (refcount_dec_and_test(&ff->count)) { - struct fuse_args *args = &ff->release_args->args; + struct fuse_args *args = &ff->release_args->args; +#ifdef CONFIG_FUSE_BPF + int err; +#endif + if (!refcount_dec_and_test(&ff->count)) + return; - if (isdir ? ff->fm->fc->no_opendir : ff->fm->fc->no_open) { - /* Do nothing when client does not implement 'open' */ - fuse_release_end(ff->fm, args, 0); - } else if (sync) { - fuse_simple_request(ff->fm, args); - fuse_release_end(ff->fm, args, 0); - } else { - args->end = fuse_release_end; - if (fuse_simple_background(ff->fm, args, - GFP_KERNEL | __GFP_NOFAIL)) - fuse_release_end(ff->fm, args, -ENOTCONN); - } - kfree(ff); +#ifdef CONFIG_FUSE_BPF + if (fuse_bpf_releasedir(&err, inode, ff)) { + fuse_release_end(ff->fm, args, 0); + } else +#endif + + if (isdir ? ff->fm->fc->no_opendir : ff->fm->fc->no_open) { + /* Do nothing when client does not implement 'open' */ + fuse_release_end(ff->fm, args, 0); + } else if (sync) { + fuse_simple_request(ff->fm, args); + fuse_release_end(ff->fm, args, 0); + } else { + args->end = fuse_release_end; + if (fuse_simple_background(ff->fm, args, + GFP_KERNEL | __GFP_NOFAIL)) + fuse_release_end(ff->fm, args, -ENOTCONN); } + kfree(ff); } struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid, @@ -243,6 +253,9 @@ int fuse_open_common(struct inode *inode, struct file *file, bool isdir) if (err) return err; + if (fuse_bpf_open(&err, inode, file, isdir)) + return err; + if (is_wb_truncate || dax_truncate) inode_lock(inode); @@ -334,7 +347,7 @@ void fuse_file_release(struct inode *inode, struct fuse_file *ff, * synchronous RELEASE is allowed (and desirable) in this case * because the server can be trusted not to screw up. */ - fuse_file_put(ff, ff->fm->fc->destroy, isdir); + fuse_file_put(ra->inode, ff, ff->fm->fc->destroy, isdir); } void fuse_release_common(struct file *file, bool isdir) @@ -374,7 +387,7 @@ void fuse_sync_release(struct fuse_inode *fi, struct fuse_file *ff, * iput(NULL) is a no-op and since the refcount is 1 and everything's * synchronous, we are fine with not doing igrab() here" */ - fuse_file_put(ff, true, false); + fuse_file_put(&fi->inode, ff, true, false); } EXPORT_SYMBOL_GPL(fuse_sync_release); @@ -930,8 +943,11 @@ static void fuse_readpages_end(struct fuse_mount *fm, struct fuse_args *args, unlock_page(page); put_page(page); } - if (ia->ff) - fuse_file_put(ia->ff, false, false); + if (ia->ff) { + WARN_ON(!mapping); + fuse_file_put(mapping ? mapping->host : NULL, ia->ff, + false, false); + } fuse_io_free(ia); } @@ -1673,7 +1689,7 @@ static void fuse_writepage_free(struct fuse_writepage_args *wpa) __free_page(ap->pages[i]); if (wpa->ia.ff) - fuse_file_put(wpa->ia.ff, false, false); + fuse_file_put(wpa->inode, wpa->ia.ff, false, false); kfree(ap->pages); kfree(wpa); @@ -1928,7 +1944,7 @@ int fuse_write_inode(struct inode *inode, struct writeback_control *wbc) ff = __fuse_write_file_get(fi); err = fuse_flush_times(inode, ff); if (ff) - fuse_file_put(ff, false, false); + fuse_file_put(inode, ff, false, false); return err; } @@ -2326,7 +2342,7 @@ static int fuse_writepages(struct address_space *mapping, fuse_writepages_send(&data); } if (data.ff) - fuse_file_put(data.ff, false, false); + fuse_file_put(inode, data.ff, false, false); kfree(data.orig_pages); out: diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 7a6cebecd00f..a133010fde1c 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1438,6 +1438,11 @@ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe, int num_entries); #ifdef CONFIG_FUSE_BPF +int fuse_bpf_open(int *err, struct inode *inode, struct file *file, bool isdir); +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 fuse_file *ff); +int fuse_bpf_releasedir(int *out, struct inode *inode, struct fuse_file *ff); int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_t offset, int whence); int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *file, int mode, loff_t offset, loff_t length); int fuse_bpf_lookup(struct dentry **out, struct inode *dir, struct dentry *entry, unsigned int flags); @@ -1445,6 +1450,27 @@ int fuse_bpf_access(int *out, struct inode *inode, int mask); #else +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 file *file) +{ + return 0; +} + +static inline int fuse_bpf_releasedir(int *out, struct inode *inode, struct 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; From patchwork Fri Mar 29 01:53:27 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609889 Received: from mail-yb1-f201.google.com (mail-yb1-f201.google.com [209.85.219.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 697A14086C for ; Fri, 29 Mar 2024 01:54:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677268; cv=none; b=QGFMzeYBpQhnub3jAyp6Za9NZ5Y4lsi7e5MO8KbiPJvRXoOU587SMW95NhZsyDnzOecsX8ibvW+wztWcDqsbnSQrlS5J9f+wIngGXmCoExEfZYVONThIz4VBFr0IdU10AEPcftzTSnFBGOndF5JhGj9CNURndMU68lgte23AHno= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677268; c=relaxed/simple; bh=JLW59NjmZEXXJzbkpxfd4s8AQSfi6of7y8Bzc4kj/Po=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=gU4CqtPqhVVCcAnadqxbg6NWaQu8PPldqZKyXTui9K6CCfmlVZRddHqZqQal339pJJBfDGqj6d2+0/yl333Wpl/dBbpgpWk4XuWVVeWF8FNvmV+NMNczUfB3zkuhKD+ot6v/6GdDVq+L87xk5w5cGQbp88bdQkH6tUpe2lcrJmc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=011HA22H; arc=none smtp.client-ip=209.85.219.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="011HA22H" Received: by mail-yb1-f201.google.com with SMTP id 3f1490d57ef6-dc647f65573so2780663276.2 for ; Thu, 28 Mar 2024 18:54:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677265; x=1712282065; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=ktsTVmyBdlTiNpg1aGMp+wH2qwaXK1AaKKS16VfXuCw=; b=011HA22Hps4cpxNx2buAPfy87SMOxluGxh315GZYczaGqPRp4j9AoAPv5j9X4RUHkl GnLdAUrxCinE4g7n7FixzGrAEYgVt4KJynpx+F/eB3YdzFr5h6BUB4oKCwfaxGJCaxek 6ZMHrsiKhPtQySHXW5cn1jLxfe/cxdTekgCnhY3t5KoCCrPR6YhHVIa+ujDwZK/XojtI vxTZCIzx8sHMXu52K9r9OXUUUor8zm586LajpDNEE65gWU431Ae9/yGPf6ZzElTCayWM vuvf2aUGrbTbBUl4M3ZjJ1wftcvdD5L7WhT46VMgjoiLHnGw9312mKQAySbOjaAH6M3o en5A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677265; x=1712282065; 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=ktsTVmyBdlTiNpg1aGMp+wH2qwaXK1AaKKS16VfXuCw=; b=EY2NBQPSiGt98CdBTpOZxRrIyqnZwtBhzJILaaVGBU5Xo6DcauYVfdHyaG/u9txzlD EQ6yyUN0nOTFBC7F32TQposC9NFjGc11ScrrZVBxCOoDzogdeMbSlrYpzD4TJO6I3AqP PUXUYXoWmuGimoSVn92VMSQu6FDeg0KLjN0YwbwcGklXJ5XxPoYkOIj/1m9liTWr4OFo ebbo2jOTjAF8c9I2OKnB/LgAbHRi2JR+U6SQCgstlub+mWYuUEQx3vRjoMw0HZMTuS8+ kCs1w76ahlZiAicYi8j27ULzoQkTecewi1Gr0ttJX8+MCqdjjT0F63CO0hU35ujMH3xB qkCA== X-Forwarded-Encrypted: i=1; AJvYcCWBKG67ucnYMvGGOQ7z9+CU6WztpkevP6saCQt8SYKkwkPkXRUyJR6fq3ZIQ1fVKLfSXRKUo7yHvhgJUIE9sdNr2W7F X-Gm-Message-State: AOJu0YyctyHtsVI+M1aRRtQ2mhwK//GcbT0vI0iviF+8EmFifJE5RxgG T3ngmHTtYiGI3XfLB2YTSSJTLu53FKf/fio+CED281/GbJK71zo8/o1c/bIaBz3v1ki4tIMFF8I MIw== X-Google-Smtp-Source: AGHT+IHwPuMmlvt3RvgB+vaylOM9ssMbHVx7dCYSMOX3mHYJDBemyI25mz8XasllVr+OtJ/G4RkvvbuGvgc= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:2503:b0:dc9:c54e:c5eb with SMTP id dt3-20020a056902250300b00dc9c54ec5ebmr307231ybb.7.1711677265603; Thu, 28 Mar 2024 18:54:25 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:27 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-13-drosen@google.com> Subject: [RFC PATCH v4 12/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC 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 317a3adbbb3e..567f859d300c 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -947,6 +947,348 @@ int fuse_revalidate_backing(struct dentry *entry, unsigned int flags) return 1; } +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 = (struct fuse_mknod_args) { + .in = (struct fuse_mknod_in) { + .mode = mode, + .rdev = new_encode_dev(rdev), + .umask = current_umask(), + }, + .name = (struct fuse_buffer) { + .data = (void *) entry->d_name.name, + .size = entry->d_name.len + 1, + .flags = BPF_FUSE_IMMUTABLE, + }, + }; + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_node_id(dir), + .opcode = FUSE_MKNOD, + }, + .in_numargs = 2, + .in_args[0] = (struct bpf_fuse_arg) { + .size = sizeof(args->in), + .value = &args->in, + }, + .in_args[1] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = &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 = fa->in_args[0].value; + struct fuse_inode *fuse_inode = get_fuse_inode(dir); + struct inode *backing_inode = fuse_inode->backing_inode; + struct path backing_path; + struct inode *inode = NULL; + + get_fuse_backing_path(entry, &backing_path); + if (!backing_path.dentry) + return -EBADF; + + inode_lock_nested(backing_inode, I_MUTEX_PARENT); + mode = fmi->mode; + if (!IS_POSIXACL(backing_inode)) + mode &= ~fmi->umask; + *out = vfs_mknod(&nop_mnt_idmap, backing_inode, backing_path.dentry, mode, + 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 = -EINVAL; + /** + * TODO: overlayfs responds to this situation with a + * lookupOneLen. Should we do that too? + */ + goto out; + } + inode = fuse_iget_backing(dir->i_sb, fuse_inode->nodeid, backing_inode); + if (IS_ERR(inode)) { + *out = 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 rdev) +{ + return 0; +} + +int fuse_bpf_mknod(int *out, struct inode *dir, struct dentry *entry, umode_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 = (struct fuse_mkdir_args) { + .in = (struct fuse_mkdir_in) { + .mode = mode, + .umask = current_umask(), + }, + .name = (struct fuse_buffer) { + .data = (void *) entry->d_name.name, + .size = entry->d_name.len + 1, + .flags = BPF_FUSE_IMMUTABLE, + }, + }; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_node_id(dir), + .opcode = FUSE_MKDIR, + }, + .in_numargs = 2, + .in_args[0] = (struct bpf_fuse_arg) { + .size = sizeof(args->in), + .value = &args->in, + }, + .in_args[1] = (struct bpf_fuse_arg) { + .value = &args->name, + .is_buffer = 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 = fa->in_args[0].value; + struct fuse_inode *fuse_inode = get_fuse_inode(dir); + struct inode *backing_inode = fuse_inode->backing_inode; + struct path backing_path; + struct inode *inode = 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 = fmi->mode; + if (!IS_POSIXACL(backing_inode)) + mode &= ~fmi->umask; + *out = 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 = lookup_one_len(entry->d_name.name, + backing_path.dentry->d_parent, + entry->d_name.len); + if (IS_ERR(d)) { + *out = PTR_ERR(d); + goto out; + } + dput(backing_path.dentry); + backing_path.dentry = d; + } + inode = fuse_iget_backing(dir->i_sb, fuse_inode->nodeid, backing_inode); + if (IS_ERR(inode)) { + *out = 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, umode_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 = (struct fuse_buffer) { + .data = (void *) entry->d_name.name, + .size = entry->d_name.len + 1, + .flags = BPF_FUSE_IMMUTABLE, + }; + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_node_id(dir), + .opcode = FUSE_RMDIR, + }, + .in_numargs = 1, + .in_args[0] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = 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 = dget_parent(backing_path.dentry); + backing_inode = d_inode(backing_parent_dentry); + + inode_lock_nested(backing_inode, I_MUTEX_PARENT); + *out = 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 = (struct fuse_buffer) { + .data = (void *) entry->d_name.name, + .size = entry->d_name.len + 1, + .flags = BPF_FUSE_IMMUTABLE, + }; + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_node_id(dir), + .opcode = FUSE_UNLINK, + }, + .in_numargs = 1, + .in_args[0] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = name, + }, + }; + + return 0; +} + +static int fuse_unlink_initialize_out(struct bpf_fuse_args *fa, struct fuse_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 = dget_parent(backing_path.dentry); + backing_inode = d_inode(backing_parent_dentry); + + inode_lock_nested(backing_inode, I_MUTEX_PARENT); + *out = 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 09bb4c63fd71..a5b6aef788b2 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -957,6 +957,10 @@ static int fuse_mknod(struct mnt_idmap *idmap, struct inode *dir, struct fuse_mknod_in inarg; struct fuse_mount *fm = get_fuse_mount(dir); FUSE_ARGS(args); + int err; + + if (fuse_bpf_mknod(&err, dir, entry, mode, rdev)) + return err; if (!fm->fc->dont_mask) mode &= ~current_umask(); @@ -1003,6 +1007,10 @@ static int fuse_mkdir(struct mnt_idmap *idmap, struct inode *dir, struct fuse_mkdir_in inarg; struct fuse_mount *fm = get_fuse_mount(dir); FUSE_ARGS(args); + int err; + + if (fuse_bpf_mkdir(&err, dir, entry, mode)) + return err; if (!fm->fc->dont_mask) mode &= ~current_umask(); @@ -1089,6 +1097,9 @@ static int fuse_unlink(struct inode *dir, struct dentry *entry) if (fuse_is_bad(dir)) return -EIO; + if (fuse_bpf_unlink(&err, dir, entry)) + return err; + args.opcode = FUSE_UNLINK; args.nodeid = get_node_id(dir); args.in_numargs = 1; @@ -1112,6 +1123,9 @@ static int fuse_rmdir(struct inode *dir, struct dentry *entry) if (fuse_is_bad(dir)) return -EIO; + if (fuse_bpf_rmdir(&err, dir, entry)) + return err; + args.opcode = FUSE_RMDIR; args.nodeid = get_node_id(dir); args.in_numargs = 1; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index a133010fde1c..9b176f78999f 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1441,6 +1441,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 isdir); 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, umode_t mode, dev_t rdev); +int fuse_bpf_mkdir(int *out, struct inode *dir, struct dentry *entry, umode_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 fuse_file *ff); int fuse_bpf_releasedir(int *out, struct inode *inode, struct fuse_file *ff); int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_t offset, int whence); @@ -1461,6 +1465,26 @@ static inline int fuse_bpf_create_open(int *out, struct inode *dir, struct dentr return 0; } +static inline int fuse_bpf_mknod(int *out, struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev) +{ + return 0; +} + +static inline int fuse_bpf_mkdir(int *out, struct inode *dir, struct dentry *entry, umode_t mode) +{ + return 0; +} + +static inline int fuse_bpf_rmdir(int *out, struct inode *dir, struct dentry *entry) +{ + return 0; +} + +static inline int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *entry) +{ + return 0; +} + static inline int fuse_bpf_release(int *out, struct inode *inode, struct file *file) { return 0; From patchwork Fri Mar 29 01:53:28 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609890 Received: from mail-yw1-f202.google.com (mail-yw1-f202.google.com [209.85.128.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ACF6D40863 for ; Fri, 29 Mar 2024 01:54:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677270; cv=none; b=j0Q+DzvXGDvse217SCuMiqERTKSAUBTPx2xIaATsmu0CeOGncxkAnPYTYls5gIj1mVmyAztU+8lkymRvNyDEQ7n7O0chrRVHf2OkmeGcfBA/RJuAQMGsm1NJGUmbukEqm9YjhyylM3RCLJ9EMmirXI2Q+59i6CLrGTbv58q2pVM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677270; c=relaxed/simple; bh=NaP8Vft/GCAaEKsbOOM9Dr7QiorAg1ARMwM5E2MqRF4=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=Ut/Fy+gl4ZI4+88MZlCjWBv8RcHjMZEuyF6Ql6XqP6szjkqiuZIgAiU1E6pJLI9AUV/mLXub/GGes4h4N8E5JNxQR5wS7YV0hoT6vELZbPYu6qO3Q71eEajjDcYnNx829K6UP+P4AY5eiQwpKbPDU4QuZ8TIdMoq5r1lre1ElkI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=lT+Ih0ob; arc=none smtp.client-ip=209.85.128.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="lT+Ih0ob" Received: by mail-yw1-f202.google.com with SMTP id 00721157ae682-60a605154d0so17552297b3.0 for ; Thu, 28 Mar 2024 18:54:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677268; x=1712282068; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=ZgfnibGcrOVFHgtkyPrSmeFKMY0WwKAtVw/Eqs3kVuY=; b=lT+Ih0obznZkTbK5lyea+SYqjPyXQzBMvcsErTjzwk+es2+00jROkZemLqpTBfBp11 3HXVKpx6Jciy9Ibyuk9Htrag/h1HL9jvTkwAh95XV9YpFqFKI5wiTBKu/78m6Z5kRlz0 oMUbINMNk5gCQzSK8ULFmkl8ZX26YFLUpR0PBXTGep6uSXuzQJPNdPm8bKyLrpp7jwAN u/Vk8/5Q6elIni/NE9+J6Z9fg4s+ieQaKiPew0JAa7+MT9FfseqpFcAEbluyhUByNDK0 xb9luCSq7DqsAExfXvbyGU4bK5A5q6VEQPTcyJHhiHJjFFgn0r9FGx8cpOiyIKjOtdBO eDrg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677268; x=1712282068; 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=ZgfnibGcrOVFHgtkyPrSmeFKMY0WwKAtVw/Eqs3kVuY=; b=j/WiPOdDj2x/LPv0B2DeU1Rv/uzNgtj9g1Z/NTv8aET9f8Ace7DAAEqArIgjiwtSlO hL2DqtKzTOtEHEmS7LnNQa6UjF3NXo+u10IOqZHLlB1njJ9nvMSNmjASMK5s1HOtNmiI SD5QZGIup7N6v6Kdz811AhFmYiGldiQsAul4AygxS197dVPHYuXcbtDjlnt8qOScWByV oQJQx8/28WCWxsAMkgkOJhGDXxCptG1zAhPKknVsVEde9uZlvwAcUnQz1LnDhk3F63bJ wEsUnBPUhrEYvmJVaq8VQrnhTZNNzecC49t0DD8BIkjZYks0jjgQ5DS0oV5zmY0ccmJL wGBg== X-Forwarded-Encrypted: i=1; AJvYcCWueM/1Hk7Dti+jwSVrwaj/X+7tMfOi0WDKmgrGl8bb3ArurUznRYyN0k8oqznrUaNsfGLAiI8IgZfzeZPIMJTqiGLT X-Gm-Message-State: AOJu0Yxkj18RtgpAPLLkc8GYoy3ToaK+x/HX1fH6zCOcEzOqoyzyLjrX mNCQE5EYNw07+iD7vKTvRSJTMYtClwnNdd/10OyOxvdKBMRl4iQJbYs1Vi7XM4YUukzhGdZ1cPt EbQ== X-Google-Smtp-Source: AGHT+IHAEp2pWokjVXFWzyRyEDQMztibYQ9ro0+bBqN+eb3ILoKKnWSV3bt5MHWyS0RsNihpNUcWDR/etXw= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a81:52cd:0:b0:611:537:2c0f with SMTP id g196-20020a8152cd000000b0061105372c0fmr289607ywb.2.1711677267815; Thu, 28 Mar 2024 18:54:27 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:28 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-14-drosen@google.com> Subject: [RFC PATCH v4 13/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC 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 | 384 ++++++++++++++++++++++++++++++++++++++ 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, 434 insertions(+), 2 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 567f859d300c..c813237b6599 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -11,6 +11,7 @@ #include #include #include +#include /* * expression statement to wrap the backing filter logic @@ -76,6 +77,95 @@ handled; \ }) +#define FUSE_BPF_IOCB_MASK (IOCB_APPEND | IOCB_DSYNC | IOCB_HIPRI | IOCB_NOWAIT | 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_file) +{ + struct inode *dst_inode; + struct inode *src_inode; + struct timespec64 dest_mtime, src_mtime, dst_ctime, src_ctime; + + if (dst_file->f_flags & O_NOATIME) + return; + + dst_inode = file_inode(dst_file); + src_inode = file_inode(src_file); + + dest_mtime = inode_get_mtime(dst_inode); + src_mtime = inode_get_mtime(src_inode); + dst_ctime = inode_get_ctime(dst_inode); + src_ctime = inode_get_ctime(src_inode); + if ((!timespec64_equal(&dest_mtime, &src_mtime) || + !timespec64_equal(&dst_ctime, &src_ctime))) { + inode_set_mtime_to_ts(dst_inode, src_mtime); + inode_set_ctime_to_ts(dst_inode, src_ctime); + } + + touch_atime(&dst_file->f_path); +} + +static void fuse_copyattr(struct file *dst_file, struct file *src_file) +{ + struct inode *dst = file_inode(dst_file); + struct inode *src = file_inode(src_file); + + inode_set_atime_to_ts(dst, inode_get_atime(src)); + inode_set_mtime_to_ts(dst, inode_get_mtime(src)); + inode_set_ctime_to_ts(dst, inode_get_ctime(src)); + i_size_write(dst, i_size_read(src)); + fuse_invalidate_attr(dst); +} + +static void fuse_file_start_write(struct file *fuse_file, struct file *backing_file, + loff_t pos, size_t count) +{ + struct inode *inode = file_inode(fuse_file); + struct fuse_inode *fi = 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 *backing_file, + loff_t pos, size_t res) +{ + struct inode *inode = file_inode(fuse_file); + struct fuse_inode *fi = 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 timespec64 *pre_atime) +{ + *pre_atime = inode_get_atime(file_inode(backing_file)); +} + +static void fuse_file_end_read(struct file *fuse_file, struct file *backing_file, + struct timespec64 *pre_atime) +{ + struct timespec64 atime = inode_get_atime(file_inode(backing_file)); + /* Mimic atime update policy of passthrough inode, not the value */ + if (!timespec64_equal(&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); @@ -635,6 +725,284 @@ int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_t o file, offset, whence); } +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 = &aio_req->iocb; + struct kiocb *iocb_orig = aio_req->iocb_orig; + struct file *filp = iocb->ki_filp; + struct file *fuse_filp = 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 = 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 = + container_of(iocb, struct fuse_bpf_aio_req, iocb); + struct kiocb *iocb_orig = 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, struct fuse_file_read_iter_args *args, + struct kiocb *iocb, struct iov_iter *to) +{ + struct file *file = iocb->ki_filp; + struct fuse_file *ff = file->private_data; + + args->in = (struct fuse_read_in) { + .fh = ff->fh, + .offset = iocb->ki_pos, + .size = to->count, + }; + + /* TODO we can't assume 'to' is a kvec */ + /* TODO we also can't assume the vector has only one component */ + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .opcode = FUSE_READ, + .nodeid = ff->nodeid, + }, .in_numargs = 1, + .in_args[0].size = sizeof(args->in), + .in_args[0].value = &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, struct fuse_file_read_iter_args *args, + struct kiocb *iocb, struct iov_iter *to) +{ + args->out = (struct fuse_read_iter_out) { + .ret = args->in.size, + }; + + fa->out_numargs = 1; + fa->out_args[0].size = sizeof(args->out); + fa->out_args[0].value = &args->out; + + return 0; +} + + +// TODO: use backing-file.c +static inline rwf_t fuse_iocb_to_rw_flags(int ifl, int iocb_mask) +{ + return ifl & iocb_mask; +} + +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 = fa->out_args[0].value; + struct file *file = iocb->ki_filp; + struct fuse_file *ff = 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 = vfs_iter_read(ff->backing_file, to, &iocb->ki_pos, + fuse_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 = -ENOMEM; + aio_req = kmem_cache_zalloc(fuse_bpf_aio_request_cachep, GFP_KERNEL); + if (!aio_req) + goto out; + + aio_req->iocb_orig = 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 = fuse_bpf_aio_rw_complete; + refcount_set(&aio_req->ref, 2); + *out = vfs_iocb_iter_read(ff->backing_file, &aio_req->iocb, to); + fuse_bpf_aio_put(aio_req); + if (*out != -EIOCBQUEUED) + fuse_bpf_aio_cleanup_handler(aio_req, *out); + } + + frio->ret = *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 = fa->out_args[0].value; + + *out = frio->ret; + + return 0; +} + +int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kiocb *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 = iocb->ki_filp; + struct fuse_file *ff = file->private_data; + + *args = (struct fuse_file_write_iter_args) { + .in.fh = ff->fh, + .in.offset = iocb->ki_pos, + .in.size = from->count, + }; + + /* TODO we can't assume 'from' is a kvec */ + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .opcode = FUSE_WRITE, + .nodeid = ff->nodeid, + }, + .in_numargs = 1, + .in_args[0].size = sizeof(args->in), + .in_args[0].value = &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 = 1; + fa->out_args[0].size = sizeof(args->out); + fa->out_args[0].value = &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 = iocb->ki_filp; + struct fuse_file *ff = file->private_data; + struct fuse_write_iter_out *fwio = fa->out_args[0].value; + ssize_t count = 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 = vfs_iter_write(ff->backing_file, from, &iocb->ki_pos, + fuse_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 = -ENOMEM; + aio_req = 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 = iocb; + kiocb_clone(&aio_req->iocb, iocb, ff->backing_file); + aio_req->iocb.ki_complete = fuse_bpf_aio_rw_complete; + refcount_set(&aio_req->ref, 2); + *out = vfs_iocb_iter_write(ff->backing_file, &aio_req->iocb, from); + fuse_bpf_aio_put(aio_req); + if (*out != -EIOCBQUEUED) + fuse_bpf_aio_cleanup_handler(aio_req, *out); + } + +out: + inode_unlock(file_inode(file)); + fwio->ret = *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 = fa->out_args[0].value; + + *out = fwio->ret; + return 0; +} + +int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kiocb *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; @@ -1335,3 +1703,19 @@ int fuse_bpf_access(int *out, struct inode *inode, int 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 = 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 284a35006462..3e1d183b6c60 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); } -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 c43f2d61c41a..3443510027a5 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1646,6 +1646,7 @@ static ssize_t fuse_file_read_iter(struct kiocb *iocb, struct iov_iter *to) struct file *file = iocb->ki_filp; struct fuse_file *ff = file->private_data; struct inode *inode = file_inode(file); + ssize_t ret; if (fuse_is_bad(inode)) return -EIO; @@ -1653,6 +1654,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); + 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 @@ -1664,6 +1668,7 @@ static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from) struct file *file = iocb->ki_filp; struct fuse_file *ff = file->private_data; struct inode *inode = file_inode(file); + ssize_t ret = 0; if (fuse_is_bad(inode)) return -EIO; @@ -1671,6 +1676,9 @@ static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from) if (FUSE_IS_DAX(inode)) return fuse_dax_write_iter(iocb, from); + 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 9b176f78999f..bd187dbf20b2 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1164,7 +1164,7 @@ int fuse_dev_init(void); void fuse_dev_cleanup(void); int fuse_ctl_init(void); -void __exit fuse_ctl_cleanup(void); +void fuse_ctl_cleanup(void); /** * Simple request sending that does request allocation and freeing @@ -1448,6 +1448,8 @@ int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *entry); int fuse_bpf_release(int *out, struct inode *inode, struct fuse_file *ff); int fuse_bpf_releasedir(int *out, struct inode *inode, struct fuse_file *ff); int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_t offset, int whence); +int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *to); +int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *from); int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *file, 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); @@ -1500,6 +1502,16 @@ static inline int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file * return 0; } +static inline int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *to) +{ + return 0; +} + +static inline int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *from) +{ + return 0; +} + static inline int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *file, int mode, loff_t offset, loff_t length) { return 0; @@ -1523,4 +1535,9 @@ int fuse_handle_backing(struct fuse_bpf_entry *fbe, struct path *backing_path); int fuse_revalidate_backing(struct dentry *entry, unsigned int flags); +#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 b47b2e41e5e4..825b65117126 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -2199,11 +2199,21 @@ static int __init fuse_init(void) if (res) goto err_sysfs_cleanup; +#ifdef CONFIG_FUSE_BPF + res = fuse_bpf_init(); + if (res) + goto err_ctl_cleanup; +#endif + sanitize_global_limit(&max_user_bgreq); sanitize_global_limit(&max_user_congthresh); return 0; +#ifdef CONFIG_FUSE_BPF + err_ctl_cleanup: + fuse_ctl_cleanup(); +#endif err_sysfs_cleanup: fuse_sysfs_cleanup(); err_dev_cleanup: @@ -2221,6 +2231,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(); } diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index 74bc15e1d0b7..8efaa9eecc5f 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -811,6 +811,11 @@ struct fuse_read_in { 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 { @@ -828,6 +833,11 @@ struct fuse_write_out { 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 { From patchwork Fri Mar 29 01:53:29 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609891 Received: from mail-yb1-f202.google.com (mail-yb1-f202.google.com [209.85.219.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E1CD44436F for ; Fri, 29 Mar 2024 01:54:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677272; cv=none; b=psGBu14qs4Rxhj25t3PSZkrW9MzHFjW6xRVW/Ev9et7bSA2yJQbGDJD4n86ASaT+i4dptp9ZPo34rZvfh0Vk16yawiqEOBjQ4oLK8sfWNiHwiuAI/y8VJC836Y6Lm8ISDFafbEKcDS2+uUMKj+tmRmnItXFWvDDadOE9t2dLwP8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677272; c=relaxed/simple; bh=6SSatiKOQPv2797ukkf5H5el6HcxXqvKZ4hc3ZglMjM=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=KFoRSQuVLYnejxia4d40iphBOJthyLcNtj/461CIKa/qgEXXrbtaMD+OmuKX8ydYAF/9EhEn/eN90IP3pDBdG+nHdYifEmVruMvkRayhMYNjclz8+Jq54yrf1a/kEP0j7WzIA7GDaV3FD9k3EVb9QudF29r/VZXtMTWPNGMZi+g= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=umdU8kKN; arc=none smtp.client-ip=209.85.219.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="umdU8kKN" Received: by mail-yb1-f202.google.com with SMTP id 3f1490d57ef6-dc6b26783b4so2186935276.0 for ; Thu, 28 Mar 2024 18:54:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677270; x=1712282070; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=jC8NburpJ0gtA38yoeQoqc6ho+qO84rwoWBCmC/NNxg=; b=umdU8kKNdDEYQlPari+AHAKgh9RMcDZANNe5jlbScNPHEInszqxpAouo5u4l6Pdj8K uNq3kY9TBRMHdnyGujp5rYamBIaHlpTHf0PEYp8IcodZ3N4YBSVzJ8cQ6r9jUXG22+QJ ydNu08vkn8phta/LqumOpmexjHG8MavdOTRPjSyhNqm+Ez6xmnhkvIij01VUsjiCywnJ d5XEyD5QV9kQXY3w+UpavVJ9ESF6c9Pgk1H5SENpnuNMfe4Uqs6x3F724Od6x2uQH2Zp lYFtIiSa4SIirEon1EQqCm5VcpATEGyl5G1/i4cNYtL1i6UTp0p4Z3i/9RliiIecusJf WuwQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677270; x=1712282070; 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=jC8NburpJ0gtA38yoeQoqc6ho+qO84rwoWBCmC/NNxg=; b=mJmQ8AbPWnwkXg2Ue4zAADz7/kfoFoMwGe9RR3nZkgg6mZcRnWgaDSCoSU0tL1HA0r tnwz6z9nPFg1ue2QCUg04wPhDmvYI9F/dWz+2Fq8pL/f17NqL8+/6KVGxY0XWfp31YS6 y23CAbARJbGFRQ3DThcqA7vn0xUg3hoyzpbX15m9uRaxgsHbXhaG/x1ZJJC6m6iPAIKB MRoYh7vX9q7hQy1dHGKZDz+2MaCTL5jEyXW3MJsxoZIFyLUSBIjwetN7B1MBGXDzEoJN E3QbgTKR0N+PnuI29pVOr+KqCnp3QuM/qmksFAyFKE3dY/XHIpHDirfAr8MNubHvz6Vv ui2Q== X-Forwarded-Encrypted: i=1; AJvYcCXXAY8yG4D5Nyph6OgSJXfCFRg4fAbnc1iYPwj65liDNe2Te2jMzMWFPZfyIFTR6CB6L3nXfOmsR4WXcSDN1nul7ep/ X-Gm-Message-State: AOJu0YwqVnOS8We2zSPZPdercaw62A4ihzEhoGuq8tY3afqh4PkPHx96 l292yKH9g8le5avuNZGnRHeST44Odn21IljqX5ud9vIoeJfV2qh/0p47vtQynMh/T9OdjuKjKDZ QGA== X-Google-Smtp-Source: AGHT+IGEkW92c6/qNS6PQZSAUYSkJgtaRBGyOZKgtytd6KAjgIrWIvmFBTD9B8z/JDA+ppvDRSBxFXfRGi0= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:160f:b0:dc6:ebd4:cca2 with SMTP id bw15-20020a056902160f00b00dc6ebd4cca2mr72791ybb.11.1711677270016; Thu, 28 Mar 2024 18:54:30 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:29 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-15-drosen@google.com> Subject: [RFC PATCH v4 14/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC This adds backing support for FUSE_READDIR Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- fs/fuse/backing.c | 202 ++++++++++++++++++++++++++++++++++++++ fs/fuse/fuse_i.h | 6 ++ fs/fuse/readdir.c | 5 + include/uapi/linux/fuse.h | 6 ++ 4 files changed, 219 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index c813237b6599..0182236c2735 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -1657,6 +1657,208 @@ int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *entry) dir, entry); } +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 fuse_read_args *args, + struct file *file, struct dir_context *ctx, + bool *force_again, bool *allow_force, bool is_continued) +{ + struct fuse_file *ff = file->private_data; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = ff->nodeid, + .opcode = FUSE_READDIR, + }, + .in_numargs = 1, + .in_args[0] = (struct bpf_fuse_arg) { + .size = sizeof(args->in), + .value = &args->in, + }, + }; + + args->in = (struct fuse_read_in) { + .fh = ff->fh, + .offset = ctx->pos, + .size = PAGE_SIZE, + }; + + *force_again = false; + *allow_force = true; + return 0; +} + +static int fuse_readdir_initialize_out(struct bpf_fuse_args *fa, struct fuse_read_args *args, + struct file *file, struct dir_context *ctx, + bool *force_again, bool *allow_force, bool is_continued) +{ + u8 *page = (u8 *)__get_free_page(GFP_KERNEL); + + if (!page) + return -ENOMEM; + + fa->flags = FUSE_BPF_OUT_ARGVAR; + fa->out_numargs = 2; + fa->out_args[0] = (struct bpf_fuse_arg) { + .size = sizeof(args->out), + .value = &args->out, + }; + fa->out_args[1] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = &args->buffer, + }; + args->out = (struct fuse_read_out) { + .again = 0, + .offset = 0, + }; + args->buffer = (struct fuse_buffer) { + .data = page, + .size = PAGE_SIZE, + .alloc_size = PAGE_SIZE, + .max_size = PAGE_SIZE, + .flags = 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 = container_of(ctx, struct fusebpf_ctx, ctx); + struct fuse_dirent *fd = (struct fuse_dirent *)(ec->addr + ec->offset); + + if (ec->offset + sizeof(struct fuse_dirent) + namelen > PAGE_SIZE) + return false; + + *fd = (struct fuse_dirent) { + .ino = ino, + .off = offset, + .namelen = namelen, + .type = d_type, + }; + + memcpy(fd->name, name, namelen); + ec->offset += FUSE_DIRENT_SIZE(fd); + + return true; +} + +static int parse_dirfile(char *buf, size_t nbytes, struct dir_context *ctx, + loff_t next_offset) +{ + char *buf_start = buf; + + while (nbytes >= FUSE_NAME_OFFSET) { + struct fuse_dirent *dirent = (struct fuse_dirent *) buf; + size_t reclen = FUSE_DIRENT_SIZE(dirent); + + if (!dirent->namelen || dirent->namelen > FUSE_NAME_MAX) + return -EIO; + if (reclen > nbytes) + break; + if (memchr(dirent->name, '/', dirent->namelen) != NULL) + return -EIO; + + ctx->pos = dirent->off; + if (!dir_emit(ctx, dirent->name, dirent->namelen, dirent->ino, + dirent->type)) { + // If we can't make any progress, user buffer is too small + if (buf == buf_start) + return -EINVAL; + else + return 0; + } + + buf += reclen; + nbytes -= reclen; + } + ctx->pos = next_offset; + + 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 = file->private_data; + struct file *backing_dir = ff->backing_file; + struct fuse_read_out *fro = fa->out_args[0].value; + struct fusebpf_ctx ec; + + ec = (struct fusebpf_ctx) { + .ctx.actor = filldir, + .ctx.pos = ctx->pos, + .addr = fa->out_args[1].buffer->data, + }; + + if (!ec.addr) + return -ENOMEM; + + if (!is_continued) + backing_dir->f_pos = file->f_pos; + + *out = iterate_dir(backing_dir, &ec.ctx); + if (ec.offset == 0) + *allow_force = false; + fa->out_args[1].buffer->size = ec.offset; + + fro->offset = ec.ctx.pos; + fro->again = 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 = fa->out_args[0].value; + struct fuse_file *ff = file->private_data; + struct file *backing_dir = ff->backing_file; + + *out = parse_dirfile(fa->out_args[1].buffer->data, fa->out_args[1].buffer->size, ctx, fro->offset); + *force_again = !!fro->again; + if (*force_again && !*allow_force) + *out = -EINVAL; + + backing_dir->f_pos = ctx->pos; + + free_page((unsigned long)fa->out_args[1].buffer->data); + return *out; +} + +int fuse_bpf_readdir(int *out, struct inode *inode, struct file *file, struct dir_context *ctx) +{ + int ret; + bool allow_force; + bool force_again = false; + bool is_continued = false; + +again: + ret = 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 >= 0) { + is_continued = 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 bd187dbf20b2..ab52003de194 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1452,6 +1452,7 @@ int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kiocb *ioc int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *from); int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *file, 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, struct dir_context *ctx); int fuse_bpf_access(int *out, struct inode *inode, int mask); #else @@ -1522,6 +1523,11 @@ static inline int fuse_bpf_lookup(struct dentry **out, struct inode *dir, struct return 0; } +static inline int fuse_bpf_readdir(int *out, struct inode *inode, struct file *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 c66a54d6c7d3..53a1fd756772 100644 --- a/fs/fuse/readdir.c +++ b/fs/fuse/readdir.c @@ -20,6 +20,8 @@ static bool fuse_use_readdirplus(struct inode *dir, struct dir_context *ctx) if (!fc->do_readdirplus) return false; + if (fi->nodeid == 0) + return false; if (!fc->readdirplus_auto) return true; if (test_and_clear_bit(FUSE_I_ADVISE_RDPLUS, &fi->state)) @@ -592,6 +594,9 @@ int fuse_readdir(struct file *file, struct dir_context *ctx) if (fuse_is_bad(inode)) return -EIO; + if (fuse_bpf_readdir(&err, inode, file, ctx)) + return err; + mutex_lock(&ff->readdir.lock); err = UNCACHED; diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index 8efaa9eecc5f..3417717c1a55 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -811,6 +811,12 @@ struct fuse_read_in { 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; From patchwork Fri Mar 29 01:53:30 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609892 Received: from mail-yw1-f202.google.com (mail-yw1-f202.google.com [209.85.128.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0CF5847F6A for ; Fri, 29 Mar 2024 01:54:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677275; cv=none; b=onwlpI+T4ANmvL/mteKCbIjoOyrj9VpRQFPSrq10JM+8qoGPPVnf9pULvkGt4LwU/1eqPbbKwQq1RK9ZD5wfzQRFjTfVf9P7W7y/VxVHwU2EkYpvmveHGFqpJrTe+6jMgXGhXzC/0zUSUP0qBZEoCXnsl3vAVEEYg3vOh9uayYw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677275; c=relaxed/simple; bh=d6XQ8+/DWf1czNCtAiutzGlVADJoXHinAhM0DHxR/QA=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=Z4cN27vW6jYDJ70psEoK+WRrx3hlbgj8FpTXBgPJ8Q+k3NqO0BozXbhJ7RPoxWQMNyqzVzX+iBDWAFQIQ7JxE8hBigXMwHfU7Y+BunQXfn3ZmXvBzAAHMbEeCtKqS8sts/pc58Xw5jUOZYfaIG7InABJ/0/6phG5SljRXLqhR/Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=GQm3uyRA; arc=none smtp.client-ip=209.85.128.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="GQm3uyRA" Received: by mail-yw1-f202.google.com with SMTP id 00721157ae682-60a2b82039bso24033627b3.1 for ; Thu, 28 Mar 2024 18:54:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677273; x=1712282073; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=3+lkznFfF063HHzwkoJDdJMsZxmqXbX6KWo4G9DjKXc=; b=GQm3uyRAKoMVVUSLsUYQj91y4SKhc5ya3owuo3Vc5YV/YAhw6ipAiGZCcCw6vzC8FE Q7cZb6A/ZoPSrJdqElMgV3jgSJKFLZHPV0DXdrOqoHiRcbeL+z5DppldRkctA3naGYJU bVcNjKhE4Mw5LU3ESFLbiGd3AeBlhWnqmrxDjUVpaKqB++BCnpISvww+U7YMEp/i+wTF FEuQgSb7VvuZCsUX+9v/Twcy/k+M7IlOnJlQS4l7q3OR8mDYo1nY9yU9F2fCwb4a525V LXWsTnkuNklbWuNIriRo60KwK31gvWpUzeRuehT3NDTID4zVp00HI5XgJzJQFsDqYtZi Y2pg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677273; x=1712282073; 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=3+lkznFfF063HHzwkoJDdJMsZxmqXbX6KWo4G9DjKXc=; b=rizkqIFcNwxd6KKbQjh7bUsIlsPEG+N3wlnqTs46opNQP9k3vWCnQIn7oSm62b/H9V 2jhAPoJCqshUJaPX3D8wuYIqZRnLHrTYEvSCQsHxgLaJot0T/3J8Dhjxg0CJDDQop483 1x+TuEAaa5hINWf3eJhpv0xtrDxIIfGeM1+t50YQQC3aMgsWcv/+6x0PRU1IWgcz4mLX FWA7f1VCtxTBE6GECmlZszoF8NH6g2631uplsjTkuuGiUCqX3bIrpTwqFSMs0tN7g7cA 7zkRzCDJO/1LUJ3SkYCJLt2VVNZhkUWmPXxdSsjwyopc8ybiJ+Xtj/YmpJPUXPQ5flq3 hvEg== X-Forwarded-Encrypted: i=1; AJvYcCVkXCuAkdfCFhwCejI/tnPnQ2fC8CLRJ/BfJYA39TZJnoy04FoMHEMP1rM+GA5MR1UJa7+p57dfEzeXjZIZK5NdIi5n X-Gm-Message-State: AOJu0YyHPRA5SDA1F6TrBu/9/u66HUFUdUi7AwvLhXWggBcVl0hpCF7b srKB7zFbjcUTtkUdCCDR5qdvui5Hc3DbJy/fOUGAtdZW+MEfgi8P8JE+hiIMhe1AOYYPc4Uha1l OGw== X-Google-Smtp-Source: AGHT+IHrCCmpyf/OqCQ46h7LCN/Lnch/y2R023tiapJl1iefGq5MDaGFFeXGiwNx4r+71k0zAdiUn/mnDts= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:160f:b0:dc6:ebd4:cca2 with SMTP id bw15-20020a056902160f00b00dc6ebd4cca2mr72793ybb.11.1711677272326; Thu, 28 Mar 2024 18:54:32 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:30 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-16-drosen@google.com> Subject: [RFC PATCH v4 15/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC 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 | 6 ++ fs/fuse/fuse_i.h | 18 ++++++ 4 files changed, 174 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 0182236c2735..c2c5cb3d3d6e 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -636,6 +636,59 @@ int fuse_bpf_releasedir(int *out, struct inode *inode, struct fuse_file *ff) fuse_release_backing, fuse_release_finalize, inode, ff); } +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 = file->private_data; + + *ffi = (struct fuse_flush_in) { + .fh = fuse_file->fh, + }; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_node_id(file->f_inode), + .opcode = FUSE_FLUSH, + }, + .in_numargs = 1, + .in_args[0].size = sizeof(*ffi), + .in_args[0].value = ffi, + .flags = 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 file *file, fl_owner_t id) +{ + struct fuse_file *fuse_file = file->private_data; + struct file *backing_file = fuse_file->backing_file; + + *out = 0; + if (backing_file->f_op->flush) + *out = 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_owner_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; @@ -725,6 +778,100 @@ int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_t o file, offset, whence); } +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 = file->private_data; + + *in = (struct fuse_fsync_in) { + .fh = fuse_file->fh, + .fsync_flags = datasync ? FUSE_FSYNC_FDATASYNC : 0, + }; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_fuse_inode(file->f_inode)->nodeid, + .opcode = FUSE_FSYNC, + }, + .in_numargs = 1, + .in_args[0].size = sizeof(*in), + .in_args[0].value = in, + .flags = 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 = file->private_data; + struct file *backing_file = fuse_file->backing_file; + const struct fuse_fsync_in *ffi = fa->in_args[0].value; + int new_datasync = (ffi->fsync_flags & FUSE_FSYNC_FDATASYNC) ? 1 : 0; + + *out = 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 fuse_fsync_in *in, + struct file *file, loff_t start, loff_t end, int datasync) +{ + struct fuse_file *fuse_file = file->private_data; + + *in = (struct fuse_fsync_in) { + .fh = fuse_file->fh, + .fsync_flags = datasync ? FUSE_FSYNC_FDATASYNC : 0, + }; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_fuse_inode(file->f_inode)->nodeid, + .opcode = FUSE_FSYNCDIR, + }, + .in_numargs = 1, + .in_args[0].size = sizeof(*in), + .in_args[0].value = in, + .flags = 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, loff_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 a5b6aef788b2..7b661fcd5470 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1785,6 +1785,9 @@ static int fuse_dir_fsync(struct file *file, loff_t start, loff_t end, if (fuse_is_bad(inode)) return -EIO; + if (fuse_bpf_dir_fsync(&err, inode, file, start, end, datasync)) + return err; + if (fc->no_fsyncdir) return 0; diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 3443510027a5..5983faf59c1f 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -510,6 +510,9 @@ static int fuse_flush(struct file *file, fl_owner_t id) if (fuse_is_bad(inode)) return -EIO; + if (fuse_bpf_flush(&err, file_inode(file), file, id)) + return err; + if (ff->open_flags & FOPEN_NOFLUSH && !fm->fc->writeback_cache) return 0; @@ -585,6 +588,9 @@ static int fuse_fsync(struct file *file, loff_t start, loff_t end, if (fuse_is_bad(inode)) return -EIO; + if (fuse_bpf_fsync(&err, inode, file, start, end, datasync)) + return err; + inode_lock(inode); /* diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index ab52003de194..256e217880c8 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1447,7 +1447,10 @@ 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 fuse_file *ff); int fuse_bpf_releasedir(int *out, struct inode *inode, struct fuse_file *ff); +int fuse_bpf_flush(int *out, struct inode *inode, struct file *file, fl_owner_t id); int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_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, loff_t start, loff_t end, int datasync); int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *to); int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *from); int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *file, int mode, loff_t offset, loff_t length); @@ -1498,11 +1501,26 @@ static inline int fuse_bpf_releasedir(int *out, struct inode *inode, struct file return 0; } +static inline int fuse_bpf_flush(int *out, struct inode *inode, struct file *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; } +static inline int fuse_bpf_fsync(int *out, struct inode *inode, struct file *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 *inode, struct kiocb *iocb, struct iov_iter *to) { return 0; From patchwork Fri Mar 29 01:53:31 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609893 Received: from mail-yw1-f201.google.com (mail-yw1-f201.google.com [209.85.128.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BF5043FB93 for ; Fri, 29 Mar 2024 01:54:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677277; cv=none; b=LlS9qPEi1Lt9Euj8PT0Ajl/oGN7e2m7SieqVagqxSi5sN90Ko3rhmdNt+7/bxdIAAO1lByTKxGHE5QwRPsigAe+pNkQrcTyXUtu/T4TaL1kVNoABbpK6FhhGV2QaU2Jw2dGIQVU/+bWzOuF7/vofgSaNGL5798bT1HU2SH++LNQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677277; c=relaxed/simple; bh=seVXvwMXgA0k5B5i3rtJiPJjVyPtrH17rjRa+Ib3NxE=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=K1jeVy4QBqnr08IYFH0eCQOEF1CjnB/uWw0r8SNEjFgx20qWu90kVqgDvQ3QHEUXuuyBRT4WoWj7N/1MAOJy+Hw4JuXhoJjg9c+vCeP5oFkkttcKE1BCEOtonIsQsMgz2EzsfwRD4Tg9aK+fcYiKCKcrHuNzdIn+Ps2A7I5e+6A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=jIas3dmj; arc=none smtp.client-ip=209.85.128.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="jIas3dmj" Received: by mail-yw1-f201.google.com with SMTP id 00721157ae682-610c23abd1fso29393557b3.0 for ; Thu, 28 Mar 2024 18:54:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677275; x=1712282075; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=4RvKFKfzlaqvNJCMsDkIXVlyZd4roHhNkix9YqBfVGw=; b=jIas3dmjUe8q1KNTkMCw7YqZ99wXpIBLGOiCNzlaG2QZ53A/iNsJpfqFHM66kRq3aF zLwwPU1Aa8HHw4KNuKcAb8ciR/2SsVv4OP4wIzUltSlB2qvmKIYyOJU15gTLrqdcb7nK TXJ05PbM7sCruXCtb2sGoxPdveKjX9cpky+ZyoylvCI4fuLVjzTz5D0V3QIkgGGfBZtc oQp5rR8nKmp3VAGxeN93cbMIMSOMq7wNX9Y1IxKvoag1D2ZwU2JuhoIDgAph/+wgXEsi 6PAfMZ1Ewh5gdaGdiDTqydnXzfPwVBdL96wY1AVRmSL54NgVEM7tvhJzGygnOyG/zxhk yt1Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677275; x=1712282075; 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=4RvKFKfzlaqvNJCMsDkIXVlyZd4roHhNkix9YqBfVGw=; b=Qm9VFQcpGFeKdKH4RDbPp3djx6aBKhpJVJ/YT5AQ2XMSKZf2XEOtQIk8io3vnMntdl 5vbCIxvEx2imFiBzpd9HGvrGdBQ3XUImi8s8R92Q4nJuhs8QaPWYTJ23WbSQYd8bZQCz ShrPPq9AbStV1v1wSl0IvqanH/nulid5BlBXpyU8jHOCA0XrypRsXgO384U4xVzbSrwa 1oOtM3W9pH8QzHL/F7Bfm6pDp0sCUv99vG/O45+0trdL8JVNoSjhqbiABHgGIZa15EH3 ClemEQE8l8QhefdVeh6SSK4EDaXRhnoXRr7FkxmDVjirCe/r1NsVA7xvHYHGOEOdiHMQ 47oQ== X-Forwarded-Encrypted: i=1; AJvYcCX20//ko7VG1Q6nAbyX/OViRIY0xUHLA1YNWCiU5ATwNjwbBNf0kZtp/CUprHlDTNUtzfcwtY9l7RurKE0cRvm9+vh3 X-Gm-Message-State: AOJu0YxW9AL63EhuTCTo7RExcCpsPyJ4tk87yR7GUzdS65tUBqXpGD9K cyXPcwW756Di03nKsxBlPAiRXOmZFC+Sn+Ye1qFjn+T4UUycUssH9Vg5zDqSC/lzemeKIRYsdaF T5w== X-Google-Smtp-Source: AGHT+IHsWOvc3Xo3vBsc98bEWBAPo7Ucv4KOtI122IiuAOiG69fT08BIPjlwpkCXP2ftn3U/Pi18R6ZCx00= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a0d:d8c5:0:b0:614:4c1:c8d with SMTP id a188-20020a0dd8c5000000b0061404c10c8dmr308429ywe.6.1711677275050; Thu, 28 Mar 2024 18:54:35 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:31 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-17-drosen@google.com> Subject: [RFC PATCH v4 16/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC 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 c2c5cb3d3d6e..79f14634ae6a 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -1735,6 +1735,256 @@ int fuse_bpf_rmdir(int *out, struct inode *dir, struct dentry *entry) dir, entry); } +static int fuse_rename_backing_common(struct inode *olddir, + struct dentry *oldent, + struct inode *newdir, + struct dentry *newent, unsigned int flags) +{ + int err = 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 = 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 = -EXDEV; + goto put_old_path; + } + if (new_backing_path.mnt != old_backing_path.mnt) { + err = -EXDEV; + goto put_new_path; + } + old_backing_dentry = old_backing_path.dentry; + new_backing_dentry = new_backing_path.dentry; + old_backing_dir_dentry = dget_parent(old_backing_dentry); + new_backing_dir_dentry = dget_parent(new_backing_dentry); + target_inode = d_inode(newent); + + trap = lock_rename(old_backing_dir_dentry, new_backing_dir_dentry); + if (trap == old_backing_dentry) { + err = -EINVAL; + goto put_parents; + } + if (trap == new_backing_dentry) { + err = -ENOTEMPTY; + goto put_parents; + } + + rd = (struct renamedata) { + .old_mnt_idmap = &nop_mnt_idmap, + .old_dir = d_inode(old_backing_dir_dentry), + .old_dentry = old_backing_dentry, + .new_mnt_idmap = &nop_mnt_idmap, + .new_dir = d_inode(new_backing_dir_dentry), + .new_dentry = new_backing_dentry, + .flags = flags, + }; + err = 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 fuse_rename2_args *args, + struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent, + unsigned int flags) +{ + *args = (struct fuse_rename2_args) { + .in = (struct fuse_rename2_in) { + .newdir = get_node_id(newdir), + .flags = flags, + }, + .old_name = (struct fuse_buffer) { + .data = (void *) oldent->d_name.name, + .size = oldent->d_name.len + 1, + .flags = BPF_FUSE_IMMUTABLE, + }, + .new_name = (struct fuse_buffer) { + .data = (void *) newent->d_name.name, + .size = newent->d_name.len + 1, + .flags = BPF_FUSE_IMMUTABLE, + }, + + }; + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_node_id(olddir), + .opcode = FUSE_RENAME2, + }, + .in_numargs = 3, + .in_args[0] = (struct bpf_fuse_arg) { + .size = sizeof(args->in), + .value = &args->in, + }, + .in_args[1] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = &args->old_name, + }, + .in_args[2] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = &args->new_name, + }, + }; + + return 0; +} + +static int fuse_rename2_initialize_out(struct bpf_fuse_args *fa, struct fuse_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 = fa->in_args[0].value; + + /* TODO: deal with changing dirs/ents */ + *out = 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 = (struct fuse_rename_args) { + .in = (struct fuse_rename_in) { + .newdir = get_node_id(newdir), + }, + .old_name = (struct fuse_buffer) { + .data = (void *) oldent->d_name.name, + .size = oldent->d_name.len + 1, + .flags = BPF_FUSE_IMMUTABLE, + }, + .new_name = (struct fuse_buffer) { + .data = (void *) newent->d_name.name, + .size = newent->d_name.len + 1, + .flags = BPF_FUSE_IMMUTABLE, + }, + + }; + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_node_id(olddir), + .opcode = FUSE_RENAME, + }, + .in_numargs = 3, + .in_args[0] = (struct bpf_fuse_arg) { + .size = sizeof(args->in), + .value = &args->in, + }, + .in_args[1] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = &args->old_name, + }, + .in_args[2] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = &args->new_name, + }, + }; + + return 0; +} + +static int fuse_rename_initialize_out(struct bpf_fuse_args *fa, struct fuse_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 = 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 7b661fcd5470..0426243d9345 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1204,6 +1204,10 @@ static int fuse_rename2(struct mnt_idmap *idmap, struct inode *olddir, return -EINVAL; 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; @@ -1215,6 +1219,9 @@ static int fuse_rename2(struct mnt_idmap *idmap, struct inode *olddir, err = -EINVAL; } } else { + if (fuse_bpf_rename(&err, olddir, oldent, newdir, newent)) + return err; + err = 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 256e217880c8..8bd78a52a6b5 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1444,6 +1444,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, umode_t mode, dev_t rdev); int fuse_bpf_mkdir(int *out, struct inode *dir, struct dentry *entry, umode_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 fuse_file *ff); int fuse_bpf_releasedir(int *out, struct inode *inode, struct fuse_file *ff); @@ -1486,6 +1491,19 @@ static inline int fuse_bpf_rmdir(int *out, struct inode *dir, struct dentry *ent return 0; } +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 dentry *oldent, + struct inode *newdir, struct dentry *newent) +{ + return 0; +} + static inline int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *entry) { return 0; From patchwork Fri Mar 29 01:53:32 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609894 Received: from mail-yw1-f201.google.com (mail-yw1-f201.google.com [209.85.128.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C66644D5BA for ; Fri, 29 Mar 2024 01:54:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677280; cv=none; b=H4m4YiSyrxnwRwz1xoDCNAnjc6tXIVDzyC3yGaUMasvz4pUNCeE64sqIuxSozi8dgTQLTq4reWhxAXPkgZmJTC0ApGOdozS3HW432+00dzVa2dnRfOVGym1HpsOowS8Q+/OaJNBYscK42TxfxxHyLNImsEcqL4qzX0EwpOdfrDs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677280; c=relaxed/simple; bh=1hYhQpZqd0XXYm/HQ/QbQPqNdnFO8JyYjGoojTz6Mc4=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=FKfXayP9folc/ZgI0rC82PnlM/PFFNqpZaxl+6IqvF9Yx1/oJtvx60dbJexCiNLtGpu3jWgaymqrbkicdvI56/lD7MkcWxmH4RnHwB/C99WtBg0+ysinUBHPO/NiJFv3RDyPnywPgsbfAPIEo6VZbm3pH1FxAi/b0lNIrqIvwhE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=ccCqjfS/; arc=none smtp.client-ip=209.85.128.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="ccCqjfS/" Received: by mail-yw1-f201.google.com with SMTP id 00721157ae682-608ad239f8fso26661547b3.0 for ; Thu, 28 Mar 2024 18:54:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677277; x=1712282077; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=IkEzYxlv6dr5xmGlGpopV2VX8GvnZFbvgbTbydYSehc=; b=ccCqjfS/bjdQ2N4Gv/Yi+fO22id8bxq5vZ01OLRgusUt1k4HCQjDvvM8MBwIFQjvjt 2fVHtGXFACvmj8i5xmTEoaZJDP/4kl+/0EniEN5tZ9MTGIgJf7WneX6fwNx3mI4gdc/l b+kJvNddTdq3ztRiPudLHHQfBLqlWB1GAGlGS8YzCAqbWZZMkepBYkZQfWfYqbevccA/ ygbNNOJTS5SP86TiF6VNXUV+tLXse9mo2/LqEHnSw0FT/K2idP1wG3j+pRYeMAWLz88g 6gsmAXaN7rJyVByztSYaQa5FPsb6UkFujI3Cs0yl2CJRQdgZGNQkpMScUSbHGoRRGUaD Ws8Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677277; x=1712282077; 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=IkEzYxlv6dr5xmGlGpopV2VX8GvnZFbvgbTbydYSehc=; b=aoD2kbUwdcO4XdZi5I0PKpoXqdF6SSOTQiqJdvRjLT7Uf2ooXNMB57z/ZKlyv+E/ZC /8DBLngAS6IJQtbnlqJDoI+B9eaGJ81kdQTpMrlvWFhQAuoZGz/SxE8HA7y5DPdntkIr b9owdUnMZSQ+AAiy0eZLlPSzCMk9tDiJPZAvSt5wxXJCBp4Jlvf3iboWCoale5Z6TnSv sqmDVxFRhsSvntdBPsjfA4la0MPaK7UDRoplHThloc1Hb6FQE2S/7U/JtXnlpwfT66Mk 9xzEJdam4gutQ43TbJr7mi65BQl2bj1YjPy4LUaT8vaZ5qH/1SbYjO7b3J6a3CO/Ejju DsBg== X-Forwarded-Encrypted: i=1; AJvYcCVPeHkUBuwsox1N4tljI+PapR7t/Af7SLFm7mHBiS16VNlR9BON7/vGfRaa5XWDTt4jReUonp/DnyXOsiYY8qPDD2+Q X-Gm-Message-State: AOJu0YzeZuqOpiRxkHyDQYt/aDB6KqjsB6jFIka3i1eBhJ8QFW1wJhiF GNs2u3oUMRPuLHnyIfB7HSKGH37hR+eP22zNPiiKNok20Ycx+99Zq9rtdKfB+iZko/n5UPXfoI3 2TQ== X-Google-Smtp-Source: AGHT+IG4o1CxL8Bjxr6xpQB23/9k8WNGx/uQfnx3fQAYtxSCzfzM/VJ9Weo4asX0Z+rhcZDoXimryo87d4s= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a0d:d897:0:b0:611:a624:12ea with SMTP id a145-20020a0dd897000000b00611a62412eamr287238ywe.0.1711677277090; Thu, 28 Mar 2024 18:54:37 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:32 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-18-drosen@google.com> Subject: [RFC PATCH v4 17/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC 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 | 296 ++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dir.c | 67 ++--------- fs/fuse/fuse_i.h | 102 ++++++++++++++++ fs/fuse/inode.c | 17 +-- 4 files changed, 412 insertions(+), 70 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 79f14634ae6a..e426268aa4e6 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -2054,6 +2054,302 @@ int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *entry) dir, entry); } +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 fuse_getattr_args *args, + const struct dentry *entry, struct kstat *stat, + u32 request_mask, unsigned int flags) +{ + args->in = (struct fuse_getattr_in) { + .getattr_flags = flags, + .fh = -1, /* TODO is this OK? */ + }; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_node_id(entry->d_inode), + .opcode = FUSE_GETATTR, + }, + .in_numargs = 1, + .in_args[0] = (struct bpf_fuse_arg) { + .size = sizeof(args->in), + .value = &args->in, + }, + }; + + return 0; +} + +static int fuse_getattr_initialize_out(struct bpf_fuse_args *fa, struct fuse_getattr_args *args, + const struct dentry *entry, struct kstat *stat, + u32 request_mask, unsigned int flags) +{ + args->out = (struct fuse_attr_out) { 0 }; + + fa->out_numargs = 1; + fa->out_args[0] = (struct bpf_fuse_arg) { + .size = sizeof(args->out), + .value = &args->out, + }; + + return 0; +} + +/* TODO: unify with overlayfs */ +static inline int fuse_do_getattr(const struct path *path, struct kstat *stat, + u32 request_mask, unsigned int flags) +{ + if (flags & AT_GETATTR_NOSEC) + return vfs_getattr_nosec(path, stat, request_mask, flags); + return vfs_getattr(path, stat, request_mask, flags); +} + +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 = &get_fuse_dentry(entry)->backing_path; + struct inode *backing_inode = backing_path->dentry->d_inode; + struct fuse_attr_out *fao = fa->out_args[0].value; + struct kstat tmp; + + if (!stat) + stat = &tmp; + + *out = fuse_do_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 = 0; + + if (fuse_invalid_attr(&outarg->attr) || + ((inode->i_mode ^ outarg->attr.mode) & S_IFMT)) { + fuse_make_bad(inode); + err = -EIO; + } else { + fuse_change_attributes(inode, &outarg->attr, NULL, + 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 = fa->out_args[0].value; + struct inode *inode = entry->d_inode; + u64 attr_version = 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 = finalize_attr(inode, outarg, attr_version, stat); + return 0; +} + +int fuse_bpf_getattr(int *out, struct inode *inode, const struct dentry *entry, 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 = arg->valid; + + if (fvalid & FATTR_MODE) + iattr->ia_valid |= ATTR_MODE, iattr->ia_mode = arg->mode; + if (fvalid & FATTR_UID) { + iattr->ia_valid |= ATTR_UID; + iattr->ia_uid = make_kuid(fc->user_ns, arg->uid); + } + if (fvalid & FATTR_GID) { + iattr->ia_valid |= ATTR_GID; + iattr->ia_gid = make_kgid(fc->user_ns, arg->gid); + } + if (fvalid & FATTR_SIZE) + iattr->ia_valid |= ATTR_SIZE, iattr->ia_size = arg->size; + if (fvalid & FATTR_ATIME) { + iattr->ia_valid |= ATTR_ATIME; + iattr->ia_atime.tv_sec = arg->atime; + iattr->ia_atime.tv_nsec = arg->atimensec; + if (!(fvalid & FATTR_ATIME_NOW)) + iattr->ia_valid |= ATTR_ATIME_SET; + } + if (fvalid & FATTR_MTIME) { + iattr->ia_valid |= ATTR_MTIME; + iattr->ia_mtime.tv_sec = arg->mtime; + iattr->ia_mtime.tv_nsec = arg->mtimensec; + if (!(fvalid & FATTR_MTIME_NOW)) + iattr->ia_valid |= ATTR_MTIME_SET; + } + if (fvalid & FATTR_CTIME) { + iattr->ia_valid |= ATTR_CTIME; + iattr->ia_ctime.tv_sec = arg->ctime; + iattr->ia_ctime.tv_nsec = 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 fuse_setattr_args *args, + struct dentry *dentry, struct iattr *attr, struct file *file) +{ + struct fuse_conn *fc = get_fuse_conn(dentry->d_inode); + + *args = (struct fuse_setattr_args) { 0 }; + iattr_to_fattr(fc, attr, &args->in, true); + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .opcode = FUSE_SETATTR, + .nodeid = get_node_id(dentry->d_inode), + }, + .in_numargs = 1, + .in_args[0].size = sizeof(args->in), + .in_args[0].value = &args->in, + }; + + return 0; +} + +static int fuse_setattr_initialize_out(struct bpf_fuse_args *fa, struct fuse_setattr_args *args, + struct dentry *dentry, struct iattr *attr, struct file *file) +{ + fa->out_numargs = 1; + fa->out_args[0].size = sizeof(args->out); + fa->out_args[0].value = &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 = get_fuse_conn(dentry->d_inode); + const struct fuse_setattr_in *fsi = fa->in_args[0].value; + struct iattr new_attr = { 0 }; + struct path *backing_path = &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 = attr->ia_valid & ~ATTR_FILE; + inode_lock(d_inode(backing_path->dentry)); + *out = notify_change(&nop_mnt_idmap, backing_path->dentry, &new_attr, + NULL); + inode_unlock(d_inode(backing_path->dentry)); + + if (*out == 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 = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_node_id(d_inode(dentry)), + .opcode = FUSE_STATFS, + }, + }; + + return 0; +} + +static int fuse_statfs_initialize_out(struct bpf_fuse_args *fa, struct fuse_statfs_out *out, + struct dentry *dentry, struct kstatfs *buf) +{ + *out = (struct fuse_statfs_out) { 0 }; + + fa->out_numargs = 1; + fa->out_args[0].size = sizeof(*out); + fa->out_args[0].value = 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 = fa->out_args[0].value; + + *out = 0; + get_fuse_backing_path(dentry, &backing_path); + if (!backing_path.dentry) + return -EBADF; + *out = vfs_statfs(&backing_path, buf); + path_put(&backing_path); + buf->f_type = 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 = 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 0426243d9345..77d231ab1d9c 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1256,7 +1256,7 @@ static int fuse_link(struct dentry *entry, struct inode *newdir, return err; } -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; @@ -1414,6 +1414,7 @@ static int fuse_do_getattr(struct inode *inode, struct kstat *stat, } 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) { @@ -1424,6 +1425,8 @@ static int fuse_update_get_attr(struct inode *inode, struct file *file, u32 inval_mask = READ_ONCE(fi->inval_mask); u32 cache_mask = fuse_get_cache_mask(inode); + if (fuse_bpf_getattr(&err, inode, path->dentry, stat, request_mask, flags)) + return err; /* FUSE only supports basic stats and possibly btime */ request_mask &= STATX_BASIC_STATS | STATX_BTIME; @@ -1469,7 +1472,7 @@ static int fuse_update_get_attr(struct inode *inode, struct file *file, int fuse_update_attributes(struct inode *inode, struct file *file, u32 mask) { - return fuse_update_get_attr(inode, file, NULL, mask, 0); + return fuse_update_get_attr(inode, file, &file->f_path, NULL, mask, 0); } int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid, @@ -1833,58 +1836,6 @@ static long fuse_dir_compat_ioctl(struct file *file, unsigned int cmd, FUSE_IOCTL_COMPAT | FUSE_IOCTL_DIR); } -static inline bool update_mtime(unsigned int ivalid, bool trust_local_mtime) -{ - /* 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 = iattr->ia_valid; - - if (ivalid & ATTR_MODE) - arg->valid |= FATTR_MODE, arg->mode = iattr->ia_mode; - if (ivalid & ATTR_UID) - arg->valid |= FATTR_UID, arg->uid = from_kuid(fc->user_ns, iattr->ia_uid); - if (ivalid & ATTR_GID) - arg->valid |= FATTR_GID, arg->gid = from_kgid(fc->user_ns, iattr->ia_gid); - if (ivalid & ATTR_SIZE) - arg->valid |= FATTR_SIZE, arg->size = iattr->ia_size; - if (ivalid & ATTR_ATIME) { - arg->valid |= FATTR_ATIME; - arg->atime = iattr->ia_atime.tv_sec; - arg->atimensec = iattr->ia_atime.tv_nsec; - if (!(ivalid & ATTR_ATIME_SET)) - arg->valid |= FATTR_ATIME_NOW; - } - if ((ivalid & ATTR_MTIME) && update_mtime(ivalid, trust_local_cmtime)) { - arg->valid |= FATTR_MTIME; - arg->mtime = iattr->ia_mtime.tv_sec; - arg->mtimensec = iattr->ia_mtime.tv_nsec; - if (!(ivalid & ATTR_MTIME_SET) && !trust_local_cmtime) - arg->valid |= FATTR_MTIME_NOW; - } - if ((ivalid & ATTR_CTIME) && trust_local_cmtime) { - arg->valid |= FATTR_CTIME; - arg->ctime = iattr->ia_ctime.tv_sec; - arg->ctimensec = iattr->ia_ctime.tv_nsec; - } -} - /* * Prevent concurrent writepages on inode * @@ -1999,6 +1950,9 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr, bool trust_local_cmtime = is_wb; bool fault_blocked = false; + if (fuse_bpf_setattr(&err, inode, dentry, attr, file)) + return err; + if (!fc->default_permissions) attr->ia_valid |= ATTR_FORCE; @@ -2178,7 +2132,8 @@ static int fuse_setattr(struct mnt_idmap *idmap, struct dentry *entry, * ia_mode calculation may have used stale i_mode. * Refresh and recalculate. */ - ret = fuse_do_getattr(inode, NULL, file); + if (!fuse_bpf_getattr(&ret, inode, entry, NULL, 0, 0)) + ret = fuse_do_getattr(inode, NULL, file); if (ret) return ret; @@ -2235,7 +2190,7 @@ static int fuse_getattr(struct mnt_idmap *idmap, return -EACCES; } - return fuse_update_get_attr(inode, NULL, stat, request_mask, flags); + return fuse_update_get_attr(inode, NULL, path, stat, request_mask, flags); } static const struct inode_operations fuse_dir_inode_operations = { diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 8bd78a52a6b5..61a17071ab02 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1460,6 +1460,10 @@ int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kiocb *ioc int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *from); int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *file, 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 *entry, 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, struct dir_context *ctx); int fuse_bpf_access(int *out, struct inode *inode, int mask); @@ -1559,6 +1563,22 @@ static inline int fuse_bpf_lookup(struct dentry **out, struct inode *dir, struct return 0; } +static inline int fuse_bpf_getattr(int *out, struct inode *inode, const struct 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 dentry *dentry, struct iattr *attr, struct file *file) +{ + return 0; +} + +static inline int fuse_bpf_statfs(int *out, struct inode *inode, struct dentry *dentry, struct kstatfs *buf) +{ + return 0; +} + static inline int fuse_bpf_readdir(int *out, struct inode *inode, struct file *file, struct dir_context *ctx) { return 0; @@ -1577,6 +1597,88 @@ int fuse_handle_backing(struct fuse_bpf_entry *fbe, struct path *backing_path); int fuse_revalidate_backing(struct dentry *entry, unsigned int flags); +static inline bool update_mtime(unsigned int ivalid, bool trust_local_mtime) +{ + /* 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 *iattr, + struct fuse_setattr_in *arg, bool trust_local_cmtime) +{ + unsigned int ivalid = iattr->ia_valid; + + if (ivalid & ATTR_MODE) + arg->valid |= FATTR_MODE, arg->mode = iattr->ia_mode; + if (ivalid & ATTR_UID) + arg->valid |= FATTR_UID, arg->uid = from_kuid(fc->user_ns, iattr->ia_uid); + if (ivalid & ATTR_GID) + arg->valid |= FATTR_GID, arg->gid = from_kgid(fc->user_ns, iattr->ia_gid); + if (ivalid & ATTR_SIZE) + arg->valid |= FATTR_SIZE, arg->size = iattr->ia_size; + if (ivalid & ATTR_ATIME) { + arg->valid |= FATTR_ATIME; + arg->atime = iattr->ia_atime.tv_sec; + arg->atimensec = iattr->ia_atime.tv_nsec; + if (!(ivalid & ATTR_ATIME_SET)) + arg->valid |= FATTR_ATIME_NOW; + } + if ((ivalid & ATTR_MTIME) && update_mtime(ivalid, trust_local_cmtime)) { + arg->valid |= FATTR_MTIME; + arg->mtime = iattr->ia_mtime.tv_sec; + arg->mtimensec = iattr->ia_mtime.tv_nsec; + if (!(ivalid & ATTR_MTIME_SET) && !trust_local_cmtime) + arg->valid |= FATTR_MTIME_NOW; + } + if ((ivalid & ATTR_CTIME) && trust_local_cmtime) { + arg->valid |= FATTR_CTIME; + arg->ctime = iattr->ia_ctime.tv_sec; + arg->ctimensec = iattr->ia_ctime.tv_nsec; + } +} + +static inline void convert_statfs_to_fuse(struct fuse_kstatfs *attr, struct kstatfs *stbuf) +{ + attr->bsize = stbuf->f_bsize; + attr->frsize = stbuf->f_frsize; + attr->blocks = stbuf->f_blocks; + attr->bfree = stbuf->f_bfree; + attr->bavail = stbuf->f_bavail; + attr->files = stbuf->f_files; + attr->ffree = stbuf->f_ffree; + attr->namelen = stbuf->f_namelen; + /* fsid is left zero */ +} + +static inline void convert_fuse_statfs(struct kstatfs *stbuf, struct fuse_kstatfs *attr) +{ + stbuf->f_type = FUSE_SUPER_MAGIC; + stbuf->f_bsize = attr->bsize; + stbuf->f_frsize = attr->frsize; + stbuf->f_blocks = attr->blocks; + stbuf->f_bfree = attr->bfree; + stbuf->f_bavail = attr->bavail; + stbuf->f_files = attr->files; + stbuf->f_ffree = attr->ffree; + stbuf->f_namelen = 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 825b65117126..bc504e0d0e80 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -696,20 +696,6 @@ static void fuse_send_destroy(struct fuse_mount *fm) } } -static void convert_fuse_statfs(struct kstatfs *stbuf, struct fuse_kstatfs *attr) -{ - stbuf->f_type = FUSE_SUPER_MAGIC; - stbuf->f_bsize = attr->bsize; - stbuf->f_frsize = attr->frsize; - stbuf->f_blocks = attr->blocks; - stbuf->f_bfree = attr->bfree; - stbuf->f_bavail = attr->bavail; - stbuf->f_files = attr->files; - stbuf->f_ffree = attr->ffree; - stbuf->f_namelen = attr->namelen; - /* fsid is left zero */ -} - static int fuse_statfs(struct dentry *dentry, struct kstatfs *buf) { struct super_block *sb = dentry->d_sb; @@ -723,6 +709,9 @@ static int fuse_statfs(struct dentry *dentry, struct kstatfs *buf) return 0; } + if (fuse_bpf_statfs(&err, dentry->d_inode, dentry, buf)) + return err; + memset(&outarg, 0, sizeof(outarg)); args.in_numargs = 0; args.opcode = FUSE_STATFS; From patchwork Fri Mar 29 01:53:33 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609895 Received: from mail-yb1-f201.google.com (mail-yb1-f201.google.com [209.85.219.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 03C3B4F887 for ; Fri, 29 Mar 2024 01:54:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677282; cv=none; b=ZbOqXAC/eko7V1tpitSXdW1mS2wTaFAh9LrsC0lhdYOIbJyIHronkkD8gUzsb0vBys17xD6uI5nbSUqjdwjs1ok9sKwXr7hkMZgoyn31JmCVqD4AtWoIM+vAYwhVR6+WJnY5o+FhO+/3XrIB98x2YRgS1Bo9y87ZDaWVEypk18Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677282; c=relaxed/simple; bh=VDSeOrmRZ4SYUgVp5dmLfIcaprrVde6t7qHG+s7E0hM=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=BLm9N7rhyaKLe5aE+1nzslOpc42SRRL3pbpi9t9gwhkq8jYBcLa9E0Hapo3nfrl7VZpd5Pf2xDR1P4/rprz7Yjd0/Op+SgJZOppMNL3wE3BYVPksXDlTymD7gmUJecWxY/SW52CVHzkbviL14XH5nLkS494IoFB5ca1wIkFejwE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=xjNF1C5Y; arc=none smtp.client-ip=209.85.219.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="xjNF1C5Y" Received: by mail-yb1-f201.google.com with SMTP id 3f1490d57ef6-dcc58cddb50so2789535276.0 for ; Thu, 28 Mar 2024 18:54:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677279; x=1712282079; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=MD93gZe61lT3bi7JodQUSrve0t6PielHvOmvulo6VTY=; b=xjNF1C5YPXePjkco0M80fBcxpb+MmIATViMfO03Q2/jEL8D8O3Ju61ojnbD8Jfr1pk /LquOyMrFOhHEg18gs6Al6S4a2ejuGaNnU5mQxKsMJzU6MVp4tJ67ILdanewwzlhhsoW EKh1hm4/ZTbt8cS8r0Od4xMG3Blkz5LmD+24ErHVbHVp/iqaP8JFSsjSxzAJ+4e9ABwB dAzeUcDsS3xvgQHr2lQuW7lYqDHnSJ6K0ViojESwwR+W1wSGsxlwjeh1V+HDz92oTHsG 1YIOHPmlXzZSiQL+P5XaG/XOxAVBUoRGUy7KI+SmW/k70Rcb6232G0ixdQiyNB2lY5aG 2VQg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677279; x=1712282079; 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=MD93gZe61lT3bi7JodQUSrve0t6PielHvOmvulo6VTY=; b=syA3sJEQzPeuXpikLSdkxJn8We1MNM5bKihIh4/F15pAtAdNBcUfZ819gtJvN0nJnz RkbbwI6do28y1QBUH5cu8wcFU83pvUAKopr8x/QBU+19rRW3ZoeALDo+5HazCjh7dKG/ cAeNUYMK/moV1Xv3IZgPA+v1TtfSJ3vraxkF5rI+9ooCoDYuPWIubRSw1uphZupMagiA 6Yq6s6aSmlp/NnUi/UlSYa+AP2yqUYUr1oyHxpAZHAGvf9TSm6tVooMMfuGQLJEvN9Sx oNAW/Cv967mjFC2sBjx6t/g5c9elZg2mlsDrexCeXYk40uG01HmleChRNdXyVJYPLp58 /pRw== X-Forwarded-Encrypted: i=1; AJvYcCW80jmhO19yL6Y2FONRYJc/t/2KTcggE9sMa5gzQ/jQM2crJAervyVaYvP1xtZ/JKuyD8rCGtrtKRvk5mWjMrFir50H X-Gm-Message-State: AOJu0YzcPBmzaWF4zki9TS5NsQ7+ztFSzGLTzgb9mFhuxCRBWXGZoIrM aLTi01xKYEsp7A2tNTpBOo29jVGwlEVELj7vg9s87ar4xI4HrABkb8C5qp7EM0X04dyhrh0glSF etw== X-Google-Smtp-Source: AGHT+IGhQUHEXr/GrzTbtSCdL1hqHgZVQAU0Jz8rAd+h7Sr956bHJaSbQ8HK4gIpji8SRFQsstttWqHj/A8= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:1884:b0:dc6:cafd:dce5 with SMTP id cj4-20020a056902188400b00dc6cafddce5mr296080ybb.12.1711677279199; Thu, 28 Mar 2024 18:54:39 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:33 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-19-drosen@google.com> Subject: [RFC PATCH v4 18/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC 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 e426268aa4e6..2363f392e915 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -11,6 +11,7 @@ #include #include #include +#include #include /* @@ -778,6 +779,92 @@ int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_t o file, offset, whence); } +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 = file_in->private_data; + struct fuse_file *fuse_file_out = file_out->private_data; + + args->in = (struct fuse_copy_file_range_in) { + .fh_in = fuse_file_in->fh, + .off_in = pos_in, + .nodeid_out = fuse_file_out->nodeid, + .fh_out = fuse_file_out->fh, + .off_out = pos_out, + .len = len, + .flags = flags, + }; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_node_id(file_in->f_inode), + .opcode = FUSE_COPY_FILE_RANGE, + }, + .in_numargs = 1, + .in_args[0].size = sizeof(args->in), + .in_args[0].value = &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 = 1; + fa->out_args[0].size = sizeof(args->out); + fa->out_args[0].value = &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 = fa->in_args[0].value; + struct fuse_file *fuse_file_in = file_in->private_data; + struct file *backing_file_in = fuse_file_in->backing_file; + struct fuse_file *fuse_file_out = file_out->private_data; + struct file *backing_file_out = fuse_file_out->backing_file; + + /* TODO: Handle changing of in/out files */ + if (backing_file_out) + *out = vfs_copy_file_range(backing_file_in, fci->off_in, backing_file_out, + fci->off_out, fci->len, fci->flags); + else + *out = splice_copy_file_range(file_in, pos_in, file_out, pos_out, len); + 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 file *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 5983faf59c1f..46de67810f03 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -3168,6 +3168,10 @@ static ssize_t __fuse_copy_file_range(struct file *file_in, loff_t pos_in, bool is_unstable = (!fc->writeback_cache) && ((pos_out + len) > inode_out->i_size); + 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; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 61a17071ab02..a95d543c79ae 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1454,6 +1454,9 @@ int fuse_bpf_release(int *out, struct inode *inode, struct fuse_file *ff); int fuse_bpf_releasedir(int *out, struct inode *inode, struct fuse_file *ff); int fuse_bpf_flush(int *out, struct inode *inode, struct file *file, fl_owner_t id); int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, loff_t offset, int whence); +int fuse_bpf_copy_file_range(ssize_t *out, struct inode *inode, struct file *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, loff_t start, loff_t end, int datasync); int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *to); @@ -1533,6 +1536,13 @@ static inline int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file * return 0; } +static inline int fuse_bpf_copy_file_range(ssize_t *out, struct inode *inode, 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 file *file, loff_t start, loff_t end, int datasync) { return 0; From patchwork Fri Mar 29 01:53:34 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609896 Received: from mail-yb1-f202.google.com (mail-yb1-f202.google.com [209.85.219.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6980552F82 for ; Fri, 29 Mar 2024 01:54:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677284; cv=none; b=YMJkwWquR1j2bR4aGErXfpoEUdZ+GF4Vq1unsrPQfvbdpe+GM0exWMUOohgPMCL9YdxfJJmUswn2mACKP0nPGC7rrE0RLtNXb4/bhB9Uc7gUJLOLM+frnB88FGBWk3s7Kcx7DxMfEJqYT5nBlD9/VhHojO8JiqsvHBQiuNg4JPc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677284; c=relaxed/simple; bh=Hn0JISNtHd2dK7T/S9cZiUrLHJgQGwSGwBy9K2nI0lA=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=o/jNpiIioIUi9fwXlmFXjIb4qrlc6rMMRUpuHgZ0QVE4Kl6WX3gqLLtaXPl9NYRWofHLwEinHXqRRliLG9K2M6yAwGkmzY5yBgK0TmLroJIN9ArCEGEHJeBuEgX0c6LkT76rDln/RLPnBgTMzKz3fp5UKdxFN6uZIwUdQex2EEM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=m0fNtm81; arc=none smtp.client-ip=209.85.219.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="m0fNtm81" Received: by mail-yb1-f202.google.com with SMTP id 3f1490d57ef6-dccc49ef73eso2278175276.2 for ; Thu, 28 Mar 2024 18:54:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677281; x=1712282081; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=FfFiI8vNK8vS8NsLoc6ESuQdhSeANMB/sBc7Z4XDU0M=; b=m0fNtm81d8rHFywtRkzbsp20ONif/i9jipiRPNNWAqSRu6jc5vbd8F2K/A37obDYJI hhjFnV6KGHIzsaSP+J71sJrM29pC1s8QSyFIo8OWpv8d5MP45Gpr8WM+N8zt3oPIy0S8 /Ny8RIEd/3SZjTQlHwaeDX8hQ5k6AgVBufY1xJQ0htaDBOCXZTAfieYsFmBsfFC99XVR JJ3d4x3JzB7lg6P81L9Ca9ogW00KabaUtN83tvjhZ7eryaiqRy1ZD/fzpWap6q6iDp1P lME6w82jKwLRPCEJ8til4/WAnNcwIlmq6lXrPSOu4asA6fC0L1PVLbQvFj14LUTq7RJg g8CQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677281; x=1712282081; 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=FfFiI8vNK8vS8NsLoc6ESuQdhSeANMB/sBc7Z4XDU0M=; b=e8H6R1hkSCslVk4vtPesU8iYNw/AIq0eYLAjqcfG5nmz4xEFt4UWFYNA9Gqke+NZLu fFoWyGk1hL6SFfjP4GpG4vN5Rad4yILXKyhvO7dyWopc9OFmsGlIhj2LYu6RDd8eIA+m /EPPxjUi1uo3ZDjBumG3kKqGQnQUVotT04VotuOx3pqvEhbohpsu/94AMhITq1Nirdtr JDlLlYCAQinHHcDrX8LqtswQ6o29c6Sd/0erckV93MtG5z2OzTbD3+oG2QGWmkDrL31C AUdiM/5Ojr2TuoTFFQV/SASYOGgEiexTi2zDawy7Bfp2LhKPj9al/M0hblxqxXd5QUe2 KGfQ== X-Forwarded-Encrypted: i=1; AJvYcCU8cDQOmHFHWmVWhkvpZ1LudaKgrpcEQawvWmgReM0gVTwjV+LjzooQX1iF3yYwwikA7N/j0svy+WtoSA2ergy2uqq4 X-Gm-Message-State: AOJu0YxiTaCLyMAeEjzmm9S0W3WzURa/EGAp/edcKUeHPKj5covsAJFk eZv1wJSvywx+QXrvoMKkH6Jt5jHFDhNeiJYyzzQN2OAAxJul5L1aDIrr8JofkXkPrVqP04doOpX NtA== X-Google-Smtp-Source: AGHT+IEEbm0qNA0/E1KnvsxrRtVriN0Qzj4b4P0HvmZKpVXzPmDJsYuuIVjd3Q4CdJv6SA5mEs9pfQvES3o= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:e0f:b0:dc6:44d4:bee0 with SMTP id df15-20020a0569020e0f00b00dc644d4bee0mr85420ybb.7.1711677281466; Thu, 28 Mar 2024 18:54:41 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:34 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-20-drosen@google.com> Subject: [RFC PATCH v4 19/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC 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 2363f392e915..3390ce66e393 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -959,6 +959,355 @@ int fuse_bpf_dir_fsync(int *out, struct inode *inode, struct file *file, loff_t file, start, end, datasync); } +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 = (struct fuse_getxattr_args) { + .in.size = size, + .name = (struct fuse_buffer) { + .data = (void *) name, + .size = strlen(name) + 1, + .max_size = XATTR_NAME_MAX + 1, + .flags = BPF_FUSE_MUST_ALLOCATE | BPF_FUSE_VARIABLE_SIZE, + }, + }; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_fuse_inode(dentry->d_inode)->nodeid, + .opcode = FUSE_GETXATTR, + }, + .in_numargs = 2, + .in_args[0] = (struct bpf_fuse_arg) { + .size = sizeof(args->in), + .value = &args->in, + }, + .in_args[1] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = &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 = size ? FUSE_BPF_OUT_ARGVAR : 0; + fa->out_numargs = 1; + if (size) { + args->value = (struct fuse_buffer) { + .data = (void *) value, + .size = size, + .alloc_size = size, + .max_size = size, + .flags = BPF_FUSE_VARIABLE_SIZE, + }; + fa->out_args[0].is_buffer = true; + fa->out_args[0].buffer = &args->value; + } else { + fa->out_args[0].size = sizeof(args->out); + fa->out_args[0].value = &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 = fa->in_args[1].buffer->data; + new_name[fa->in_args[1].buffer->size - 1] = 0; + } + ret = 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 = ret; + else + ((struct fuse_getxattr_out *)fa->out_args[0].value)->size = 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 = fa->out_args[0].buffer->size; + return 0; + } + + fgo = fa->out_args[0].value; + + *out = 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 = (struct fuse_getxattr_args) { + .in.size = size, + }; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_fuse_inode(dentry->d_inode)->nodeid, + .opcode = FUSE_LISTXATTR, + }, + .in_numargs = 1, + .in_args[0] = + (struct bpf_fuse_arg) { + .size = sizeof(args->in), + .value = &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 = 1; + + if (size) { + args->value = (struct fuse_buffer) { + .data = list, + .size = size, + .alloc_size = size, + .max_size = size, + .flags = BPF_FUSE_VARIABLE_SIZE, + }; + fa->flags = FUSE_BPF_OUT_ARGVAR; + fa->out_args[0].is_buffer = true; + fa->out_args[0].buffer = &args->value; + } else { + fa->out_args[0].size = sizeof(args->out); + fa->out_args[0].value = &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 = 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 = *out; + else + ((struct fuse_getxattr_out *)fa->out_args[0].value)->size = *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 = fa->out_args[0].buffer->size; + return 0; + } + + fgo = fa->out_args[0].value; + *out = fgo->size; + return 0; +} + +int fuse_bpf_listxattr(ssize_t *out, struct inode *inode, struct dentry *dentry, + 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 = (struct fuse_setxattr_args) { + .in = (struct fuse_setxattr_in) { + .size = size, + .flags = flags, + }, + .name = (struct fuse_buffer) { + .data = (void *) name, + .size = strlen(name) + 1, + .max_size = XATTR_NAME_MAX + 1, + .flags = BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, + }, + .value =(struct fuse_buffer) { + .data = (void *) value, + .size = size, + .max_size = XATTR_SIZE_MAX, + .flags = BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, + }, + }; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_fuse_inode(dentry->d_inode)->nodeid, + .opcode = FUSE_SETXATTR, + }, + .in_numargs = 3, + .in_args[0] = (struct bpf_fuse_arg) { + .size = sizeof(args->in), + .value = &args->in, + }, + .in_args[1] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = &args->name, + }, + .in_args[2] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = &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, struct dentry *dentry, + const char *name, const void *value, size_t size, + int flags) +{ + // TODO Ensure we actually use filter values + *out = 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, struct 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 = (struct fuse_buffer) { + .data = (void *) name, + .size = strlen(name) + 1, + .max_size = XATTR_NAME_MAX + 1, + .flags = BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, + }; + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_fuse_inode(dentry->d_inode)->nodeid, + .opcode = FUSE_REMOVEXATTR, + }, + .in_numargs = 1, + .in_args[0] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = 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 = &get_fuse_dentry(dentry)->backing_path; + + /* TODO account for changes of the name by prefilter */ + *out = 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 *dentry, 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 a95d543c79ae..127758575ddc 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1459,6 +1459,13 @@ int fuse_bpf_copy_file_range(ssize_t *out, struct inode *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, loff_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 *dentry, 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 *dentry, const char *name); int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *to); int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *from); int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *file, int mode, loff_t offset, loff_t length); @@ -1553,6 +1560,29 @@ static inline int fuse_bpf_dir_fsync(int *out, struct inode *inode, struct file return 0; } +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, struct 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, struct dentry *dentry, const char *name) +{ + return 0; +} + static inline int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kiocb *iocb, struct iov_iter *to) { return 0; diff --git a/fs/fuse/xattr.c b/fs/fuse/xattr.c index 5b423fdbb13f..394441397a8a 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; + if (fuse_bpf_listxattr(&ret, inode, entry, list, size)) + return ret; + if (!fuse_allow_current_process(fm->fc)) return -EACCES; @@ -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; + if (fuse_bpf_getxattr(&err, inode, dentry, name, value, size)) + return err; + return fuse_getxattr(inode, name, value, size); } @@ -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; + if (value) + handled = fuse_bpf_setxattr(&err, inode, dentry, name, value, size, flags); + else + handled = fuse_bpf_removexattr(&err, inode, dentry, name); + if (handled) + return err; + if (!value) return fuse_removexattr(inode, name); From patchwork Fri Mar 29 01:53:35 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609897 Received: from mail-yb1-f201.google.com (mail-yb1-f201.google.com [209.85.219.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9D2805466C for ; Fri, 29 Mar 2024 01:54:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677287; cv=none; b=SzQ/IxTf5EiC+aweIBxZUIF79fIoUpFPmNQFp1DdVSQAO/9+lO7f70GrGaimJ0qtLadrUGSyaf9d83OQ7rM8rkJ8ivRcsLjS+Jua42qSjs/Tp7UECW5wpWg45CZYtT19IHe8QuN0n0tLGNSwNqYa/tmP9JDyvJF4XOYd152At2U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677287; c=relaxed/simple; bh=QfML3qc9UJHMHe73CHYhpNf8LXROJQ2njklWSzdnUWw=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=mo1JdJ/WJDRa8lvQyFFhN4/OQG0hQtyZbct3tPPzmS24+RmkqiARzOZdlgQqdrVQJoLAwlc54TJf/cgmbilI4Wnis5f82nHBmR1QAY7pc1E4VIGYJ1TY0Al4fH3gx1CqzK5/Uh+ikcTnZPVoAGeid1ee6AT/a5hEnUCDepOPhtM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=SSBrSuSY; arc=none smtp.client-ip=209.85.219.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="SSBrSuSY" Received: by mail-yb1-f201.google.com with SMTP id 3f1490d57ef6-dcdc3db67f0so3088198276.1 for ; Thu, 28 Mar 2024 18:54:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677283; x=1712282083; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=3P1bhmbT6iU17mlNzbCJY7IYTyE6K2uvRKWPaZ4NGDM=; b=SSBrSuSYVwJeix6FZq2iwk2avUZOibF9DgBrT5sUJZHioKN3GsGsg5oz4uRE/83+jG Wkr5q0k+KDKknTcxIMANKe8oKtjYLr2dOm3SKMQyXc/Lo9CmbVmg+Qfx2nQPK7EhpltW htebZfSX/M08Ps5k9VW1dcVupLZ8mdGBcEbDgvrJJLwUpv1smsgqPg+GMUEMPv36Qyh4 MWzEbILAJ9jw2Fm+50A1nLQJbQPbeJGXct1YAjyj67c+YPNfx1bYhkScq+/x5SWmLZA/ FV+U3cO6HGIoMadU5ZMkxpSunXJfCxBdcqnY0ZhmukatSmcNP2FD61upZhHDCJNomd/Y KAqw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677283; x=1712282083; 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=3P1bhmbT6iU17mlNzbCJY7IYTyE6K2uvRKWPaZ4NGDM=; b=SiSUIiYPLOh3I7BRmqbRdVnCEkiHitYdrnLmjf/BQlyg3DwyTtKzPsA+51CsQgD88W xcDfHCsZHtIno/iSXEXiQYS0lhNoBSyewt6VzvtGaNAPpv3U+Oge4hKah2n7iRm/pDB1 VFavkYTLnNDjzQm2INDapUNFTKMWL9mXbU6JMhph626/bRk8aH9tCeFHUZGITLr89EsV YTjFwg3767J4P66gzFgQE4ATQSbBcVZrIeLGxBUgNy0vs//g+sPdKGPzEFpM7kqwXPxD vtwEzbOneNyXVbAH93o29ISpL0jH5D3TMzIi376B03hbUuStFevy4hMT0HikQnzBcEoj alxA== X-Forwarded-Encrypted: i=1; AJvYcCX1+pln5nCx02vK2th3orZQBvmIYrJo/r8xqyyk3GI6fyKjuB1QgpSc1OxONhAJ5jBKudWjMFWbFLrWO+bSNmNpRHyv X-Gm-Message-State: AOJu0YwMefomDnBGqNFtczZimRxk/dbvMlD2t5yUYwtuJ4qkmKRDzlKI PJWbQ7jjkZPer/6yRYttw5hUVIkrcDvwSMH0nYG5zrBsa5Fl67Z68CJgp/th+vFg/Py5qKNO8fJ b4A== X-Google-Smtp-Source: AGHT+IHgSug0jzbXQ+VMbPe8iA95JXmQc6vNb8Aqcyo87ghk95a5rdtG5sohFlX0zvcPZBd2OCeTo3AxAyA= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a25:c753:0:b0:dd3:f55f:ff02 with SMTP id w80-20020a25c753000000b00dd3f55fff02mr840561ybe.1.1711677283612; Thu, 28 Mar 2024 18:54:43 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:35 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-21-drosen@google.com> Subject: [RFC PATCH v4 20/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC 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 3390ce66e393..f18aee297335 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -2490,6 +2490,125 @@ int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *entry) dir, entry); } +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_link_args *args, + struct dentry *entry, struct inode *dir, + struct dentry *newent) +{ + struct inode *src_inode = entry->d_inode; + + *args = (struct fuse_link_args) { + .in = (struct fuse_link_in) { + .oldnodeid = get_node_id(src_inode), + }, + .name = (struct fuse_buffer) { + .data = (void *) newent->d_name.name, + .size = newent->d_name.len + 1, + .max_size = NAME_MAX + 1, + .flags = BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, + }, + }; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .opcode = FUSE_LINK, + }, + .in_numargs = 2, + .in_args[0] = (struct bpf_fuse_arg) { + .size = sizeof(args->in), + .value = &args->in, + }, + .in_args[1] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = &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 dentry *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 = NULL; + struct fuse_inode *fuse_dir_inode = get_fuse_inode(dir); + struct inode *backing_dir_inode = fuse_dir_inode->backing_inode; + + *out = 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 = -EBADF; + goto err_dst_path; + } + + backing_dir_dentry = dget_parent(backing_new_path.dentry); + backing_dir_inode = d_inode(backing_dir_dentry); + + inode_lock_nested(backing_dir_inode, I_MUTEX_PARENT); + *out = 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 = -EINVAL; + /** + * TODO: overlayfs responds to this situation with a + * lookupOneLen. Should we do that too? + */ + goto out; + } + + fuse_new_inode = fuse_iget_backing(dir->i_sb, fuse_dir_inode->nodeid, backing_dir_inode); + if (IS_ERR(fuse_new_inode)) { + *out = 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 dentry *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; @@ -2786,6 +2905,214 @@ int fuse_bpf_statfs(int *out, struct inode *inode, struct dentry *dentry, struct dentry, buf); } +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 fuse_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 = (struct fuse_get_link_args) { + .name = (struct fuse_buffer) { + .data = (void *) dentry->d_name.name, + .size = dentry->d_name.len + 1, + .max_size = NAME_MAX + 1, + .flags = BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, + }, + }; + + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .opcode = FUSE_READLINK, + .nodeid = get_node_id(inode), + }, + .in_numargs = 1, + .in_args[0] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = &args->name, + }, + }; + + return 0; +} + +static int fuse_get_link_initialize_out(struct bpf_fuse_args *fa, struct fuse_get_link_args *args, + struct inode *inode, struct dentry *dentry, + struct delayed_call *callback) +{ + // TODO +#if 0 + args->path = (struct fuse_buffer) { + .data = NULL, + .size = 0, + .max_size = PATH_MAX, + .flags = BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, + }; + fa->out_numargs = 1; + fa->out_args[0].is_buffer = true; + fa->out_args[0].buffer = &args->path; +#endif + + return 0; +} + +static int fuse_get_link_backing(struct bpf_fuse_args *fa, const char **out, + struct inode *inode, struct dentry *dentry, + struct delayed_call *callback) +{ + struct path backing_path; + + if (!dentry) { + *out = ERR_PTR(-ECHILD); + return PTR_ERR(*out); + } + + get_fuse_backing_path(dentry, &backing_path); + if (!backing_path.dentry) { + *out = 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 = 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 **out, + 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 fuse_symlink_args *args, + struct inode *dir, struct dentry *entry, const char *link, int len) +{ + *args = (struct fuse_symlink_args) { + .name = (struct fuse_buffer) { + .data = (void *) entry->d_name.name, + .size = entry->d_name.len + 1, + .flags = BPF_FUSE_IMMUTABLE, + }, + .path = (struct fuse_buffer) { + .data = (void *) link, + .size = len, + .max_size = PATH_MAX, + .flags = BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, + }, + }; + *fa = (struct bpf_fuse_args) { + .info = (struct bpf_fuse_meta_info) { + .nodeid = get_node_id(dir), + .opcode = FUSE_SYMLINK, + }, + .in_numargs = 2, + .in_args[0] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = &args->name, + }, + .in_args[1] = (struct bpf_fuse_arg) { + .is_buffer = true, + .buffer = &args->path, + }, + }; + + return 0; +} + +static int fuse_symlink_initialize_out(struct bpf_fuse_args *fa, struct fuse_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 = get_fuse_inode(dir); + struct inode *backing_inode = fuse_inode->backing_inode; + struct path backing_path; + struct inode *inode = NULL; + + *out = 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 = 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 = -EINVAL; + /** + * TODO: overlayfs responds to this situation with a + * lookupOneLen. Should we do that too? + */ + goto out; + } + inode = fuse_iget_backing(dir->i_sb, fuse_inode->nodeid, backing_inode); + if (IS_ERR(inode)) { + *out = 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, const 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 77d231ab1d9c..08f93a981665 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1033,6 +1033,10 @@ static int fuse_symlink(struct mnt_idmap *idmap, struct inode *dir, struct fuse_mount *fm = get_fuse_mount(dir); unsigned len = strlen(link) + 1; FUSE_ARGS(args); + int err; + + if (fuse_bpf_symlink(&err, dir, entry, link, len)) + return err; args.opcode = FUSE_SYMLINK; args.in_numargs = 2; @@ -1239,6 +1243,9 @@ static int fuse_link(struct dentry *entry, struct inode *newdir, struct fuse_mount *fm = get_fuse_mount(inode); FUSE_ARGS(args); + if (fuse_bpf_link(&err, inode, entry, newdir, newent)) + return err; + memset(&inarg, 0, sizeof(inarg)); inarg.oldnodeid = get_node_id(inode); args.opcode = FUSE_LINK; @@ -1741,12 +1748,16 @@ static const char *fuse_get_link(struct dentry *dentry, struct inode *inode, { struct fuse_conn *fc = get_fuse_conn(inode); struct page *page; + const char *out = NULL; int err; err = -EIO; if (fuse_is_bad(inode)) goto out_err; + if (fuse_bpf_get_link(&out, inode, dentry, callback)) + return out; + if (fc->cache_symlinks) return page_get_link(dentry, inode, callback); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 127758575ddc..8ae6ad967f95 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1450,6 +1450,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, struct inode *dir, struct dentry *newent); int fuse_bpf_release(int *out, struct inode *inode, struct fuse_file *ff); int fuse_bpf_releasedir(int *out, struct inode *inode, struct fuse_file *ff); int fuse_bpf_flush(int *out, struct inode *inode, struct file *file, fl_owner_t id); @@ -1474,6 +1475,9 @@ int fuse_bpf_getattr(int *out, struct inode *inode, const 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, const char *link, int len); int fuse_bpf_readdir(int *out, struct inode *inode, struct file *file, struct dir_context *ctx); int fuse_bpf_access(int *out, struct inode *inode, int mask); @@ -1523,6 +1527,11 @@ static inline int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *en return 0; } +static inline int fuse_bpf_link(int *out, struct inode *inode, struct dentry *entry, struct inode *dir, struct dentry *newent) +{ + return 0; +} + static inline int fuse_bpf_release(int *out, struct inode *inode, struct file *file) { return 0; @@ -1619,6 +1628,17 @@ static inline int fuse_bpf_statfs(int *out, struct inode *inode, struct dentry * return 0; } +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 dentry *entry, const char *link, int len) +{ + return 0; +} + static inline int fuse_bpf_readdir(int *out, struct inode *inode, struct file *file, struct dir_context *ctx) { return 0; From patchwork Fri Mar 29 01:53:36 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609898 Received: from mail-yb1-f202.google.com (mail-yb1-f202.google.com [209.85.219.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B4B2B54FA5 for ; Fri, 29 Mar 2024 01:54:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677288; cv=none; b=AUOeWFVK21VlSJzJ3IrPenHbRP+x8vv3/tCBVwKtCs001yZ8T96YGFchYGi3kFXbXq9nvCutBS2zgeKei6b4EtsPPRog52shfa+SLeZy40Ejde1wQoxH82x6LIuXKYizCyVwljIMhP1SkFv6/EVoi5hmM5w598/gJi4SBTZ3x7U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677288; c=relaxed/simple; bh=bBfAJi/bJf2nD+gd3YqaNeaEuDQ3ztMPp8XYXKoJ52g=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=A52V1W4d6KmOs4RzlehVHw3rGoIgAxd7ey+2wOEc32XA+Jqi4EUmnEtkhsnvXq95R0Ks+r9SAfVFqKyAVkYvdxvUELnRPidQkZUB+JjHJAJmujadD++jgN0jU7eoMA8QDBNGe7lyZz4VfPrQfutGQ2YPbkhHH/pV+AV5O34rQ70= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=wHJkKVjR; arc=none smtp.client-ip=209.85.219.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="wHJkKVjR" Received: by mail-yb1-f202.google.com with SMTP id 3f1490d57ef6-dc6b2682870so2426298276.0 for ; Thu, 28 Mar 2024 18:54:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677286; x=1712282086; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=RHfrMplbbUkEBek0oxfkmQFW+uLxDMPloBNof976q78=; b=wHJkKVjRMmLu3l+quU/T1dkRy2eN9n0/f3raXxrUmdxkEBl+F5MjZr/xg38d4MvCqS Zs9QTc8jOGKXFRO4lO1MZBy7urTGovN9UqoSDGIj4vkCU6yKuKVfKoMFe23nx7gPicwU qTndnx7gWcpxrRQE3Bd68SISe31PeCm+I1cUHij18e1P5B9gpTPn5zi1CCK0q6EzeBqm Tc6Q/fcJVszPKaxFXH52l8WwjB31mbv0hsR5M7dzl9cGBNJFiLE8X9zQbPjt7yi13eTb +hYrgs/m/hkK3FRhKnopDAs5EHcEA4vG5Ef4vtT6QkBqDzMPAh39LsuLInkr63qWHXkW YRvg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677286; x=1712282086; 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=RHfrMplbbUkEBek0oxfkmQFW+uLxDMPloBNof976q78=; b=VgVK9a5RMReS/iGfo7GUNQX+CUyVRYnCniltHLiVqEx2RVAEx23AZYP+lnvsR4m8tE JfwW0Tjrkfz7Y5BYtVVVynU2wNGcMmrOXfRw1c5sOKg6WF0wHiPLjcoW1Mb44TrepE61 8I2BkdJrBMIlishDAXanlJj7q7XBhxI4ESRpV6xzR9RZxzyiG3U081a3RcSyXOZygFPH jn6wHkp+V+WzmrlWzEGdkWnJPh8d4JiSuuJdy0CMP0dETwU8ySR706bZJA3KSZAf8CaL zp4tab/6KCz9rxwwqvsqm6ofgYw9PMookm46DwnCB/kHUrxIlEU6NaOikbRShzCSSo3K 0QCg== X-Forwarded-Encrypted: i=1; AJvYcCV2KkFasxIHtDsouCXszPST6lnMolVvTSeqLA7gZUD/NGWj89MqBe9kqB1zIXq41R0UQ+XGyedvieffMMDbeaCnzpjx X-Gm-Message-State: AOJu0Yzm72jW5zIUnnDu+Kv6nrPBRgiuv7Qnse07xfhZho6vJy+6+XUN 0cvafx4uFrWljmdp4Gr6lvWjaKx3g0sa3zn2A80wW/J2qefqqSBzFgB77/t6SUI+WlJOoLmiqAP BXQ== X-Google-Smtp-Source: AGHT+IFbnUa67u4V59nNCnGnTBVkSthpFf64uvZOHCdvmkQja1eGLLGkihEUhtkYy01teYap9rh/yjJgyyU= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:120a:b0:dda:c59c:3953 with SMTP id s10-20020a056902120a00b00ddac59c3953mr322226ybu.0.1711677285863; Thu, 28 Mar 2024 18:54:45 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:36 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-22-drosen@google.com> Subject: [RFC PATCH v4 21/36] fuse-bpf: Add partial flock 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-State: RFC This adds passthrough support for flock on fuse-bpf files. It does not give any control via a bpf filter. The flock will act as though it was taken on the lower file. see fuse_test -t32 (flock_test) Signed-off-by: Daniel Rosenberg --- fs/fuse/backing.c | 15 +++++++++++++++ fs/fuse/file.c | 9 +++++++-- fs/fuse/fuse_i.h | 1 + 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index f18aee297335..b2df2469c29c 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -1586,6 +1587,20 @@ int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kiocb *io iocb, from); } +int fuse_file_flock_backing(struct file *file, int cmd, struct file_lock *fl) +{ + struct fuse_file *ff = file->private_data; + struct file *backing_file = ff->backing_file; + int error; + + fl->fl_file = backing_file; + if (backing_file->f_op->flock) + error = backing_file->f_op->flock(backing_file, cmd, fl); + else + error = locks_lock_file_wait(backing_file, fl); + return error; +} + 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 46de67810f03..255eb59d04f8 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -2676,13 +2676,18 @@ static int fuse_file_flock(struct file *file, int cmd, struct file_lock *fl) { struct inode *inode = file_inode(file); struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_file *ff = file->private_data; int err; +#ifdef CONFIG_FUSE_BPF + /* TODO - this is simply passthrough, not a proper BPF filter */ + if (ff->backing_file) + return fuse_file_flock_backing(file, cmd, fl); +#endif + if (fc->no_flock) { err = locks_lock_file_wait(file, fl); } else { - struct fuse_file *ff = file->private_data; - /* emulate flock with POSIX locks */ ff->flock = true; err = fuse_setlk(file, fl, 1); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 8ae6ad967f95..e69f83616909 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1651,6 +1651,7 @@ static inline int fuse_bpf_access(int *out, struct inode *inode, int mask) #endif // CONFIG_FUSE_BPF +int fuse_file_flock_backing(struct file *file, int cmd, struct file_lock *fl); 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_path); From patchwork Fri Mar 29 01:53:37 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609899 Received: from mail-yb1-f201.google.com (mail-yb1-f201.google.com [209.85.219.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D37EB55E56 for ; Fri, 29 Mar 2024 01:54:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677290; cv=none; b=m6IfhbAqp3tlqkt9Y8iC83KWpIV0hqnU1bNYaDQRqXJHISig8FXhKR2U7Yz4iazE/rYN+dHoPS1WQkPdlmJfmvinMx6Sr0u8DaYRRvRfNEEBmZjYQTNxlYPigmB/HiHYWTHVy/RTAkFZTERhZHjbTnB5JvSLvWSkRk0HJdIW3k4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677290; c=relaxed/simple; bh=khFObfDZD9RDH2x00diHiavJfq0/SktiZtvPXG5MlzE=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=nsrM0iA7FUUXW2bG62OcIZeP9hf80ZeOMrPowspeDGJ7c9tOcSOqE+SQynCp8dqS7ehDaz0VRwqMC6+oh8onYd4ni4NHCQjRlrhOwssXF+Bd9c2eXkynofDHe64P5r3CUXXrqSqKxaNwxk4MhyO30T07Zzn3kvJHvtjpGpE2bBI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=jmoc2SZv; arc=none smtp.client-ip=209.85.219.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="jmoc2SZv" Received: by mail-yb1-f201.google.com with SMTP id 3f1490d57ef6-dbf618042daso2325989276.0 for ; Thu, 28 Mar 2024 18:54:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677288; x=1712282088; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=NDLJEVuG9Rbj6C1vTwZ9ezw7r88gt9y1LMBKHsbBCE8=; b=jmoc2SZvk2aoUv1xz4bPrYSGvddGhft76CPXgi/v7tXp7O5BwobqQIiovVGV+mVsNY hd0y/SWSzjjvxR078oLEaPhwChSrO4ySP+34oO0/U9Hm8dzsaRWUE9mlbRWemiyHhS+U pblEFhV82fiFoY6eeSt1Dmmuin6UMTycDAlX9N4wfCDNcMFjVUg7sqIT9BxYKUiFAmzT GXSyAimRqT8XfuDlKTdxO1FtP5I+xGhb6h7gP20CyRD2+L8fitMe5jTPtcJcPcDoLpi9 9MvK4xIfGtLifSmtxkLNNp7XTE/dHhnAXM4pkUvRtsLs18S6BNBbTdCFKeXI+Q8j35by dUeg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677288; x=1712282088; 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=NDLJEVuG9Rbj6C1vTwZ9ezw7r88gt9y1LMBKHsbBCE8=; b=R6eIHpctBPo7XGvgrgghENl1IlJK2ZV/rut7Vjdj7gtWJuoy/FxazSCzRPU2+vBFHy JgBYrdABITkm01e5uBx3/cruSPyG+olVKAK2pyCMswbr7pl7ABw6DrdtvvMItEC3GwBW DrW9mGZaoxS1F6oITMJJqjjzf+Bvr1WTa/l05APj3SPaxd5jQx6/RJzdYzKpQyAhaKw3 G3Ao2yYJPg4MAW0fK7+1uJfPdIg53J9lI8Ze8PvCLLuVK0FYTzi0uErev+NzQlEFKcQB ylAexi0RRI5OT7VWwao7ZJhj0p32NL6mGvRvQZ5vtdoeQ191pXVh3zWWKO4t3XIi76zR f6zA== X-Forwarded-Encrypted: i=1; AJvYcCVPAonhexQ3UkEACqbUEeMdCgC9yIGMB5P3pQoy9J3AMFldVDMST8AJgO4UeXs/3M1bwBlQxSSX2oeqJwHSw/bgeO3p X-Gm-Message-State: AOJu0YwsEuuupGWKZja5Y6iobLaMkmoIIlCAR87ismu4gfBy+EF3Vpdx 5BBflqH6BaS9FNa8zuzfSvgNyoE9lXMoGqOiitQTKYL9Mz9zT4l8IXq7VkzzNVBFdd/xHEC0pgY zKQ== X-Google-Smtp-Source: AGHT+IGAbEDexCw+N2JysnI9m0JGtmH2E2g+ERbDEaavjOuwDP/sU418H9UcdiCecEIveINGY+GMY2/U38o= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:2481:b0:dd9:2a64:e98a with SMTP id ds1-20020a056902248100b00dd92a64e98amr86513ybb.9.1711677288087; Thu, 28 Mar 2024 18:54:48 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:37 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-23-drosen@google.com> Subject: [RFC PATCH v4 22/36] fuse-bpf: Add partial ioctl 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-State: RFC This adds passthrough only support for ioctls with fuse-bpf. compat_ioctls will return -ENOTTY. Signed-off-by: Daniel Rosenberg --- fs/fuse/backing.c | 13 +++++++++++++ fs/fuse/fuse_i.h | 2 ++ fs/fuse/ioctl.c | 9 +++++++++ 3 files changed, 24 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index b2df2469c29c..884c690becd5 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -1587,6 +1587,19 @@ int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kiocb *io iocb, from); } +long fuse_backing_ioctl(struct file *file, unsigned int command, unsigned long arg, int flags) +{ + struct fuse_file *ff = file->private_data; + long ret; + + if (flags & FUSE_IOCTL_COMPAT) + ret = -ENOTTY; + else + ret = vfs_ioctl(ff->backing_file, command, arg); + + return ret; +} + int fuse_file_flock_backing(struct file *file, int cmd, struct file_lock *fl) { struct fuse_file *ff = file->private_data; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index e69f83616909..81639c006ac5 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1651,6 +1651,8 @@ static inline int fuse_bpf_access(int *out, struct inode *inode, int mask) #endif // CONFIG_FUSE_BPF +long fuse_backing_ioctl(struct file *file, unsigned int command, unsigned long arg, int flags); + int fuse_file_flock_backing(struct file *file, int cmd, struct file_lock *fl); ssize_t fuse_backing_mmap(struct file *file, struct vm_area_struct *vma); diff --git a/fs/fuse/ioctl.c b/fs/fuse/ioctl.c index 8929dfec4970..d40dace24d2b 100644 --- a/fs/fuse/ioctl.c +++ b/fs/fuse/ioctl.c @@ -360,6 +360,15 @@ long fuse_ioctl_common(struct file *file, unsigned int cmd, if (fuse_is_bad(inode)) return -EIO; +#ifdef CONFIG_FUSE_BPF + { + struct fuse_file *ff = file->private_data; + + /* TODO - this is simply passthrough, not a proper BPF filter */ + if (ff->backing_file) + return fuse_backing_ioctl(file, cmd, arg, flags); + } +#endif return fuse_do_ioctl(file, cmd, arg, flags); } From patchwork Fri Mar 29 01:53:38 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609900 Received: from mail-yb1-f202.google.com (mail-yb1-f202.google.com [209.85.219.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 529BE6A35F for ; Fri, 29 Mar 2024 01:54:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677295; cv=none; b=F7dTap6JPjkL03tDfNgVKV+BXAphqZltYa8MdlaTorUEEie67EPoCXDLEDUoPQOiHY0RSIHjRIggPfHSXZtIuwSNHsZXRQaNXqTuGv9JZPjuKWMIJhJQErn7MRzRF9ggaWmGWqJBhRN4mqZNwSgfs/f1c/kN0TXu5cqMyjNTZZ0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677295; c=relaxed/simple; bh=lg0g6uOF3iAO/ssiLRAcrWry2HIbZpoqzls2TxKy5ms=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=kb+x73CoEtoGQTOnetK5ErspRiqAWJEVI2Rz5MtAJPsbwWbdek38w//vu7ebXrRBvpkwLwPqrABzdliJJmZQLi5TyAOuIFTirN2i0FaeXNxfvf5QHoREMQ/+YCKkWeNOOwl403m1cLTGBcxmmZ7HNJQQ4r9Al1AmG84H4qblV98= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=x4mYQJZa; arc=none smtp.client-ip=209.85.219.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="x4mYQJZa" Received: by mail-yb1-f202.google.com with SMTP id 3f1490d57ef6-dce775fa8adso2712973276.1 for ; Thu, 28 Mar 2024 18:54:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677292; x=1712282092; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=od0eLderVP8wkPdPrI4ENNCsb2rG+V1Fgj/sF5+lSuM=; b=x4mYQJZa5N8yMZBMXbwKAQkbqX4tgVORq6SXljK1YvZG2YQaow9FUihPpsUQy/7sxB 87U18Ceo/QJ08muJc84lHbltw6/mtmtbxT5hOsBKyulrS8wbBCX32tj4sdiXPR9Ud9t7 +Fr03dmcXrLT2FL2gsKS5bfDPbarhDJeH4fgs0JMx3ws3tYlQaeYbFmfXcxcLerRaIzk Y8VCX13ytsiUWeIGEunk3HaMpjQyIkm1Fo/yqOvUhcGcDd5g2TCe0c5n/nFY/OAxNNTi 6gI0gbIdeP+wZVVjwvv4b+vmTpz/MtanosebtA27LStRbdTzra+LyHE4U70meWDng0BN 7iIQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677292; x=1712282092; 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=od0eLderVP8wkPdPrI4ENNCsb2rG+V1Fgj/sF5+lSuM=; b=MrdktscnfNVBj935+zgpZGCrsyj3JXh5qpY95VpggHFG7tORq5OJylAUnjYwxg9fZr RvpF8qkyOjEU2FyUZ0E2NYAHbSMdAhD2r8cL3zc6BM4Ey1ehUyq+jpM04EDZYcCPpZZY wceVSkpmXzIK8vTMm/RdcdaTPYQI37j1uiSGPR993oDXTqmjIMf5QdMXq+9zZY+/q8O8 erA0lXv0RW+0wBhUQVjC9zRocwlVirhlYMdrY0bGw4o7JSw6cJ+GWAXASJZc966laBb8 ueThG8/xzKZYVGrCt8+jleDy7KgrKekwhJVKmiYodoxJvJyfOKnaUV+D80W++rRsMTXz SZwQ== X-Forwarded-Encrypted: i=1; AJvYcCUF2ME0wyfD9Hxw1RW23Pg8TTLNtvjzytRZutnFv4J1hBPS2cHdkCakoAsPpoEA/yb9rKkdImdsLeQsj7u/sdaMEs4n X-Gm-Message-State: AOJu0YxbV4Gt2Q0KdYnUNYY0QN2F/4aKj+UOo/vQQgq8oRidzCoIw00E 1x9T1e7zEFE45bf0KwYBPzXYs6eYlb0yjlR48Dax8tXtKpmGmAtMLqHlh3NKzGqLl3ef+RGymLZ gdA== X-Google-Smtp-Source: AGHT+IFNeB7P0RtQ53mC8zg19/JpPgaA07h8+WRkHIS3dG7DeGgWdytZdjnC5CdjHQubEzDWWlIhriwU/f0= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:2841:b0:dcb:bc80:8333 with SMTP id ee1-20020a056902284100b00dcbbc808333mr293843ybb.13.1711677292440; Thu, 28 Mar 2024 18:54:52 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:38 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-24-drosen@google.com> Subject: [RFC PATCH v4 23/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence X-Patchwork-State: RFC 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 81639c006ac5..f1a8f8a97f1f 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -586,6 +586,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; @@ -873,6 +874,9 @@ struct fuse_conn { /* Is statx not implemented by fs? */ unsigned int no_statx:1; + /** BPF Only, no Daemon running */ + unsigned int no_daemon:1; + /** The number of requests waiting for completion */ atomic_t num_waiting; diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index bc504e0d0e80..b4332416e23a 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -829,6 +829,7 @@ enum { OPT_MAX_READ, OPT_BLKSIZE, OPT_ROOT_DIR, + OPT_NO_DAEMON, OPT_ERR }; @@ -844,6 +845,7 @@ static const struct fs_parameter_spec fuse_fs_parameters[] = { fsparam_u32 ("blksize", OPT_BLKSIZE), fsparam_string ("subtype", OPT_SUBTYPE), fsparam_u32 ("root_dir", OPT_ROOT_DIR), + fsparam_flag ("no_daemon", OPT_NO_DAEMON), {} }; @@ -933,6 +935,11 @@ static int fuse_parse_param(struct fs_context *fsc, struct fs_parameter *param) return invalfc(fsc, "Unable to open root directory"); break; + case OPT_NO_DAEMON: + ctx->no_daemon = true; + ctx->fd_present = true; + break; + default: return -EINVAL; } @@ -1506,7 +1513,7 @@ void fuse_send_init(struct fuse_mount *fm) ia->args.nocreds = true; ia->args.end = process_init_reply; - if (fuse_simple_background(fm, &ia->args, GFP_KERNEL) != 0) + if (unlikely(fm->fc->no_daemon) || fuse_simple_background(fm, &ia->args, GFP_KERNEL) != 0) process_init_reply(fm, &ia->args, -ENOTCONN); } EXPORT_SYMBOL_GPL(fuse_send_init); @@ -1798,6 +1805,7 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx) fc->destroy = ctx->destroy; fc->no_control = ctx->no_control; fc->no_force_umount = ctx->no_force_umount; + fc->no_daemon = ctx->no_daemon; err = -ENOMEM; root = fuse_get_root_inode(sb, ctx->rootmode, ctx->root_dir); @@ -1844,7 +1852,7 @@ static int fuse_fill_super(struct super_block *sb, struct fs_context *fsc) struct fuse_fs_context *ctx = fsc->fs_private; int err; - if (!ctx->file || !ctx->rootmode_present || + if (!!ctx->file == ctx->no_daemon || !ctx->rootmode_present || !ctx->user_id_present || !ctx->group_id_present) return -EINVAL; @@ -1852,10 +1860,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 != &fuse_dev_operations) || - (ctx->file->f_cred->user_ns != sb->s_user_ns)) - return -EINVAL; - ctx->fudptr = &ctx->file->private_data; + if (ctx->file) { + if ((ctx->file->f_op != &fuse_dev_operations) || + (ctx->file->f_cred->user_ns != sb->s_user_ns)) + return -EINVAL; + ctx->fudptr = &ctx->file->private_data; + } err = fuse_fill_super_common(sb, ctx); if (err) @@ -1905,6 +1915,9 @@ static int fuse_get_tree(struct fs_context *fsc) fsc->s_fs_info = fm; + if (ctx->no_daemon) + return get_tree_nodev(fsc, fuse_fill_super);; + if (ctx->fd_present) ctx->file = fget(ctx->fd); From patchwork Fri Mar 29 01:53:39 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609901 X-Patchwork-Delegate: bpf@iogearbox.net Received: from mail-yw1-f201.google.com (mail-yw1-f201.google.com [209.85.128.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7D7096A03E for ; Fri, 29 Mar 2024 01:54:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677298; cv=none; b=qpGSu86OSH3iuVHVExA+b6dkKPyuR2ar6ubYjJKmaFsOJMsuThtRVYufjHK/GlxHy6PDk071suXDwX8QZv/eLoFZ7nC6i/c34FiV8Tyjd5jtHEtmZckqOveqdc8J0npllYX5XZUcdl/zs8s6ZUow0VBPAyTBf7VDRXTB+p/6FrA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677298; c=relaxed/simple; bh=pk9Ej0arFqeZkCMlr0ALuLSLryIAG82IwMSP2yyNpaA=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=phslBRFHJKQlQEgcLUz9t1c8kBluvCn4xmCfrpVYbvd2r+UodaTKKLIJbvE6XwgKWvSkhz/cqPV1J2EXjsQzkK5wOgoBl43vfUdSFNrw/mah+BV/7jzRiNNzgp1BqgT9sfSK/k+HkxsBhIUReAx8OdTDLkHs4qj1oPDcb/baC10= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=c0KGDrY2; arc=none smtp.client-ip=209.85.128.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="c0KGDrY2" Received: by mail-yw1-f201.google.com with SMTP id 00721157ae682-60cd62fa20fso30395367b3.3 for ; Thu, 28 Mar 2024 18:54:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677295; x=1712282095; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=+fgPCyEdk35RPimkCV3pdmwq6y+0q0t2sbyu7i903QQ=; b=c0KGDrY2SPDw7zdMWf7UKCH98zvhJzG5zHgEHZyv7iZqIWji2kfPU5fOBB0d+q7/tC sbc7rMYqc6L8RFBccXpQcrptd2BE1gQratTe3haaO2f8/xKijqG9k2WRACjCsoLy8NHd jei0sJMtzUZEah1ZaIuKQrxYyYgdSyrwVDRCLfZ7tFj0fSD5utRFrdQMe37n18DjgUaM wLN4HU0S9IDoEAgpwkPoEByCwPsq+dkz2chMXrVLay6J01QpCsAsQJkVfaPK56J/Uqa+ WgWA9kIDia9oaW3km2QfVIYRnSedBxB+pKe87ftQGxhMBcW+J2CPYt0m66ERPTH2QYMb kpTg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677295; x=1712282095; 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=+fgPCyEdk35RPimkCV3pdmwq6y+0q0t2sbyu7i903QQ=; b=I312t3Mf8m9jo/LmHSuzaPymbSVhSRzVL2Ky+HfxzBLTp2fTqIHPfPRRRw+vVz2Q3P A0J2ANCK/HwKQp291I8O3+x812Y9AdgM3PC72gVOwfUEUqcrNw0Xk6L9tpcEZQ07+tUp bos07nJJbzSG321Rnf67435E0h4SFw2V2i78QG7uzyPHmTglDOd172U7jSzsS0fcLXWN 4aS8PWZPyLLAKc+H2gg6rRI0/43DEOAgByqCTl4OuWvvqtd3hqpU4DJctTO1lATpmkuc Pq1I/rv8CVokbCIPtLajTPwZ0bPcR9/WIGpFGJPLKD+Z1QGRLJenhVPrj9dzN1VAw0rM VPNQ== X-Forwarded-Encrypted: i=1; AJvYcCUwkwog7eMwt054yCZ2i3rxESbJWV43iLn/OC0PTAaRWLTP7gnj0c8GxsnZmivr/HR40m0IaAMwe8OoxVKofMa2w6Qg X-Gm-Message-State: AOJu0Yzmmz1iYlyvUQ2fWUQTdlz5TyCSW9LnvNkOsVIGBhT1e8Lats9+ TrQXXXoJjBZ/qxREevvzzNqlEPJqXxtssQ8i48EQV1mAyme2/dBivmDfAFZTmeNYQRZjwHEqhwQ zsw== X-Google-Smtp-Source: AGHT+IHo+ItJCz1vEMlMJs25DEazsRdzqnfW39HVVXO67q+uyT3d5CUr2ZMLHU+KkcK6U1vReIfsPel7clY= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a0d:d9c8:0:b0:613:eb1a:6407 with SMTP id b191-20020a0dd9c8000000b00613eb1a6407mr266377ywe.9.1711677294719; Thu, 28 Mar 2024 18:54:54 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:39 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-25-drosen@google.com> Subject: [RFC PATCH v4 24/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-Delegate: bpf@iogearbox.net X-Patchwork-State: RFC 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 85ec7fc799d7..46c1a3e3166d 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -7461,4 +7461,16 @@ struct bpf_iter_num { __u64 __opaque[1]; } __attribute__((aligned(8))); +/* 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__ */ From patchwork Fri Mar 29 01:53:40 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609902 X-Patchwork-Delegate: bpf@iogearbox.net Received: from mail-yb1-f201.google.com (mail-yb1-f201.google.com [209.85.219.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8A2807BB17 for ; Fri, 29 Mar 2024 01:54:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677299; cv=none; b=mrSIi0yyn2Ow5qKY0zygOfQSLp1TIRaw1U9ZRGOnUiy3cJ4f7sDtiejD8FmrbOrEPR5FncgFyBpRph8vVVqyyVzVO4sordtqyNQPeyxEKBYu6++SVncW4+3/PcJnJGVoMacL2Uh6jRNfWdfByHnBIq2tXloOBLWYvpgJ+VK3TGY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677299; c=relaxed/simple; bh=9iXCNP+EW0+2+JC2nl2OoNy89uZqvybM8NUQJ2K/ih4=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=pKziXDPWzcE6fuyB4XNHdPyvEtXHn3hYkjQG9uycfctAKwS8y9uGLGMGQDaODW7UdjURgxf7FRw3YJ6kzv7eR0zUmj6nyqUqC6JD7lnXmBqRatWdU9xg/JCPtLIr3xd/acfz+FPJuRJWtRZEAbAIvFJ+goUaEDJozOCeC8CFOBg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=RH0NkW3B; arc=none smtp.client-ip=209.85.219.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="RH0NkW3B" Received: by mail-yb1-f201.google.com with SMTP id 3f1490d57ef6-dd0ae66422fso2959478276.0 for ; Thu, 28 Mar 2024 18:54:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677297; x=1712282097; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=RmiND9fJytE7hp4aS6EZvp2Qf4FYwUyLzLHSobY4D+U=; b=RH0NkW3B64RQtwllzuYjxkJLeWIXvju90EGmkhpDAz0nu27JSAa8QfHI3qW91Vqow+ eRXrh0VfoR/RqJXz9uPpOW9nnkwroxtdVxgvB4pEoRVZdrKSJ/BaFrWhT8nhwXouMuE7 DuVQbnWdIAXCWUWT4cEr9RUtloHYA0IxI7huyL16H040Xc5u8FQg7bGz53WOyIGrEjgS zJS2In2dshIrjarZ0Y6nr8UbNEn9WON+ce0oQnx0RKdEoIjFOCbiLF6Cth/OKtBVlIeq LBEdaDwUPogsXUH6HIvcXNM472ZnUFGwQENthMc1Bc1hsSGKCE27Y7oR0gYUmYXWeaER 4x8g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677297; x=1712282097; 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=RmiND9fJytE7hp4aS6EZvp2Qf4FYwUyLzLHSobY4D+U=; b=Wvgm2sznepZM1oXuL/AtwFV6uzePE+bo6f3+NGRQf91+RNkCQQWQUEuVNZZuhwC3uK erLY1d/6KUqhpoIzcw9NikcpbtVQkx1iUH2fYL6zy/nIGgqVGNN4wNh7OX3uOGt3RMk6 VHUodGn4HxuEURZr8ef08pdN3ClwQvT2Krq0IoGKql+VeDyHVveAVCjdX0P6YP1qY81G HbNBEomXJL9VVkePPfQoX/nOupdg3HLF2rVe2V19J2jazeR2ihii+2F52ztvyaEltBJL tDRpW1V5dLqAKaxDcffwZW9vO0c/AtwEod1ZddNPmMhoC2UKa+e37D2ho0Ol5/QBo+D3 gKgg== X-Forwarded-Encrypted: i=1; AJvYcCXklJh5/9LGCbREpXvv66BzdaZDAsXH+DVHtTG3a6n8ig4Df2CKgTDCXiZwyC9o/EApNXnd1gUxSOVcffmDEQL9NPrq X-Gm-Message-State: AOJu0YzQRjw2ACkIbD6PvZ968GT/wRrMMayShDhk3ZhLUmI2DhA5nzrQ Po11s12HdMogr/f/d7DFwCnJU1Hhaxw17BfcxST8C2RtZSTDreZiFyrps9e2aQ2IGU6VCliwgfy f8A== X-Google-Smtp-Source: AGHT+IHeiHkMKp379qbywYMCr+diDVdq9JP2CxLadyp3LUxzIJLjH2ayb4pU1zwCCzZ0jFp6BgvxfUk/s6M= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:1004:b0:dda:c4ec:7db5 with SMTP id w4-20020a056902100400b00ddac4ec7db5mr365495ybt.4.1711677296744; Thu, 28 Mar 2024 18:54:56 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:40 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-26-drosen@google.com> Subject: [RFC PATCH v4 25/36] bpf: Increase struct_op max members 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-Delegate: bpf@iogearbox.net X-Patchwork-State: RFC Fuse bpf goes a bit past the '64' limit here. This doubles the limit, although fuse-bpf currently uses 66. Signed-off-by: Daniel Rosenberg --- include/linux/bpf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 785660810e6a..7a5a806e97ad 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1636,7 +1636,7 @@ struct bpf_token { struct bpf_struct_ops_value; struct btf_member; -#define BPF_STRUCT_OPS_MAX_NR_MEMBERS 64 +#define BPF_STRUCT_OPS_MAX_NR_MEMBERS 128 /** * struct bpf_struct_ops - A structure of callbacks allowing a subsystem to * define a BPF_MAP_TYPE_STRUCT_OPS map type composed From patchwork Fri Mar 29 01:53:41 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609904 X-Patchwork-Delegate: bpf@iogearbox.net Received: from mail-yw1-f202.google.com (mail-yw1-f202.google.com [209.85.128.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9FB1985295 for ; Fri, 29 Mar 2024 01:54:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677303; cv=none; b=UjPXT4ddJwljFOlcGUGcYwnvO0bkLuae2VIoSo4rBFIqnSQX5jNkLozALYJWg1xo6ScLYDLeMr/A3OwQWnqGydL2AOqO62+rBwjfPt20NYc/GrWu+78hXCgGSD1s8ESez6nizkFCmsWJRMTA0eIodJ4ATucXIpBdOUnMXCZ/Hzs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677303; c=relaxed/simple; bh=Et5mTBi93e0VpaY4zXqcHzFa+ntH32rehSMxUCipauA=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=lF+4+C7Ei+zLcYu5230/UjB/BSIt5zeuC6Yb6LNBME79eCH9WrlX5MM5fe/JcXoExp1lRUGN8wF5wBEg9bJU1KFlpRcOZM4dA/r4rN+coXgI/zY3yQJAoENs9Wl93FyuoBnxda6sxCFw0yy86kiADWQ1qy7RTG7/SRCnrNqSOHo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=nEqtjOAu; arc=none smtp.client-ip=209.85.128.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="nEqtjOAu" Received: by mail-yw1-f202.google.com with SMTP id 00721157ae682-60a0b18e52dso19456557b3.1 for ; Thu, 28 Mar 2024 18:54:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677299; x=1712282099; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=3RVx3rpuKP4Z/vxm8rCI0dcufWl8IOQWTFksEMcfQDY=; b=nEqtjOAu2JpC0J1fzeoGlU/whruVHnfzgFu1OFslhaXi/gJGXaUBtJMfe1ddzLdQHr BqGWgZ22U9hUdXUPknpaVf/gIQuUzhvEUdTX1w+stRCv7PX/3i/g893DDBgmOqA5vTeQ 0ZcLYHi5SxthiAnpcaWI5pSE7j0IiTNfPkJx4heh3VvJCOArsQQlRFGadz+OqQMA3UZ8 mXecfmPKbaDNjW9qzfbhXifk+gK+HyxUimeb8CK8VNlrq6osS7hVhl3HXWJ5uIAR/MiX B24z6uSNiQiRAkkwAMHJtiecRPGxmUmDktcfT+Nre+oOJ4/DgpSMEhfQxwRotJ5pfn7c QWZA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677299; x=1712282099; 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=3RVx3rpuKP4Z/vxm8rCI0dcufWl8IOQWTFksEMcfQDY=; b=meSpDaERi9zgTTdL7RdWHviIYuYHGKCL5BDjkEiOhH3n6nQQ0azjE95awBjmgRChnq CVyzStDCU87C/DJ4Nb1JGvHRYi8mzhgNdZw/VcWCv98kilsE31yKUrjXSv06wyzv2Oj7 5RUfVNFP18Co+oaJ4OfF3/dzXa026M6bLXgC8i6wd3bS/VwIQFcUHg0bJwCxhg04HJBc M0tIkVwz5rA0OU1FnjdtXfRDRNPJlDgR0xr8ZtFtJX02cLpx9O9Qg+tXBkvqX24kbF8N gbYHRB9Z2NUyoaf00EhUo2JZqWVaOz1SJgNv0aYA8sklayC1VPGdFYhny0NYLR1bb0r6 IpRg== X-Forwarded-Encrypted: i=1; AJvYcCWFwy4s2WlCYyNTjlPPh0Jjaa2qqoDnTjvK2ylqWb117jfubhUUUsi3u0crc7A7TRxUyIEjdVra73no2cId6FYeWkfh X-Gm-Message-State: AOJu0Yz+83VcgNzoX2lob9lbvEDAVLpy9dJPlGWpMGvKYdfZsMsIVPmC tSjTiykwZhA69xClweBkq3WIKoYTMVgX6AhSFFe4qibPK/8U+ZjHIOU1BNNvgj2TiuZXkOBk6dd Hog== X-Google-Smtp-Source: AGHT+IGlzdw8aXILGmvK5Z65XAKp1rgWPf8oXWGLBbptXj4ClFeTaNoSsKS8izYyiS5spNoIZHXahnhskHo= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a81:914d:0:b0:614:447e:fbcd with SMTP id i74-20020a81914d000000b00614447efbcdmr71014ywg.2.1711677298927; Thu, 28 Mar 2024 18:54:58 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:41 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-27-drosen@google.com> Subject: [RFC PATCH v4 26/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-Delegate: bpf@iogearbox.net X-Patchwork-State: RFC 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 | 716 +++++++++++++++++++++++++++++++++++++++ kernel/bpf/btf.c | 1 + kernel/bpf/verifier.c | 10 +- 5 files changed, 919 insertions(+), 1 deletion(-) 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 to the buffer #define BPF_FUSE_ALLOCATED (1 << 4) // The helper function allocated the buffer +extern void *bpf_fuse_get_writeable(struct fuse_buffer *arg, u64 size, bool copy); + /* * BPF Fuse Args * @@ -81,4 +83,191 @@ static inline unsigned bpf_fuse_arg_size(const struct bpf_fuse_arg *arg) return arg->is_buffer ? arg->buffer->size : arg->size; } +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 *meta, + struct fuse_copy_file_range_in *in); + 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); + + 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 == 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 == 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 to 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 *meta, + struct fuse_fallocate_in *in); + uint32_t (*file_fallocate_postfilter)(const struct bpf_fuse_meta_info *meta, + 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 4ce95acfcaa7..1fc22dfc8bee 100644 --- a/kernel/bpf/Makefile +++ b/kernel/bpf/Makefile @@ -46,3 +46,7 @@ obj-$(CONFIG_BPF_PRELOAD) += preload/ obj-$(CONFIG_BPF_SYSCALL) += 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) += bpf_fuse.o +endif diff --git a/kernel/bpf/bpf_fuse.c b/kernel/bpf/bpf_fuse.c new file mode 100644 index 000000000000..7ae93b5230c7 --- /dev/null +++ b/kernel/bpf/bpf_fuse.c @@ -0,0 +1,716 @@ +// 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 <= arg->size && + (!(arg->flags & BPF_FUSE_MUST_ALLOCATE) || + (arg->flags & BPF_FUSE_ALLOCATED))) { + if (arg->flags & BPF_FUSE_VARIABLE_SIZE) + arg->size = size; + arg->flags |= 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 = arg->size; + } + + if (size != arg->size && size > arg->max_size) + return 0; + + /* If our buffer is big enough, just adjust size */ + if (size <= arg->alloc_size) { + if (!copy) + arg->size = size; + arg->flags |= BPF_FUSE_MODIFIED; + return arg->data; + } + + writeable_val = kzalloc(size, GFP_KERNEL); + if (!writeable_val) + return 0; + + arg->alloc_size = size; + /* If we're copying the buffer, assume the same amount is used. If that isn't the case, + * caller must change size. Otherwise, assume entirety of new buffer is used. + */ + if (copy) + memcpy(writeable_val, arg->data, (arg->size > size) ? size : arg->size); + else + arg->size = size; + + if (arg->flags & BPF_FUSE_ALLOCATED) + kfree(arg->data); + arg->data = writeable_val; + + arg->flags |= BPF_FUSE_ALLOCATED | BPF_FUSE_MODIFIED; + + return arg->data; +} +EXPORT_SYMBOL(bpf_fuse_get_writeable); + +__bpf_kfunc_start_defs(); +__bpf_kfunc void bpf_fuse_get_rw_dynptr(struct fuse_buffer *buffer, struct bpf_dynptr_kern *dynptr__uninit, u64 size, bool copy) +{ + buffer->data = bpf_fuse_get_writeable(buffer, size, copy); + bpf_dynptr_init(dynptr__uninit, buffer->data, BPF_DYNPTR_TYPE_LOCAL, 0, buffer->size); +} + +__bpf_kfunc void bpf_fuse_get_ro_dynptr(const struct fuse_buffer *buffer, struct bpf_dynptr_kern *dynptr__uninit) +{ + bpf_dynptr_init(dynptr__uninit, buffer->data, BPF_DYNPTR_TYPE_LOCAL, 0, buffer->size); + bpf_dynptr_set_rdonly(dynptr__uninit); +} + +__bpf_kfunc uint32_t bpf_fuse_return_len(struct fuse_buffer *buffer) +{ + return buffer->size; +} +__diag_pop(); +__bpf_kfunc_end_defs(); +BTF_KFUNCS_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_KFUNCS_END(fuse_kfunc_set) + +static const struct btf_kfunc_id_set bpf_fuse_kfunc_set = { + .owner = THIS_MODULE, + .set = &fuse_kfunc_set, +}; + +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, prog); + } +} + +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 = btf_type_by_id(reg->btf, reg->btf_id); + if (t == 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 = { + .get_func_proto = bpf_fuse_get_func_proto, + .is_valid_access = bpf_fuse_is_valid_access, + .btf_struct_access = 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 = (const struct fuse_ops *)udata; + f_ops = (struct fuse_ops *)kdata; + + moff = __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)) <= 0) + return -EINVAL; + return 1; + } + + return 0; +} + +static int bpf_fuse_init(struct btf *btf) +{ + s32 type_id; + + type_id = btf_find_by_name_kind(btf, "fuse_buffer", BTF_KIND_STRUCT); + if (type_id < 0) + return -EINVAL; + fuse_buffer_struct_type = btf_type_by_id(btf, type_id); + + return 0; +} + +static struct bpf_fuse_ops_attach *fuse_reg = 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 = reg_ops; + return 0; +} +EXPORT_SYMBOL_GPL(register_fuse_bpf); + +void unregister_fuse_bpf(struct bpf_fuse_ops_attach *reg_ops) +{ + if (reg_ops == fuse_reg) + fuse_reg = NULL; + else + pr_warn("Refusing to unregister unregistered FUSE"); +} +EXPORT_SYMBOL_GPL(unregister_fuse_bpf); + +/* CFI Stubs */ +static uint32_t bpf_fuse_default_filter(const struct bpf_fuse_meta_info *meta) +{ + return 0; +} + +static uint32_t bpf_fuse_open_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_open_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_open_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_open_in *in, + struct fuse_open_out *out) +{ + return 0; +} + +static uint32_t bpf_fuse_opendir_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_open_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_opendir_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_open_in *in, + struct fuse_open_out *out) +{ + return 0; +} + +static uint32_t bpf_fuse_create_open_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_create_in *in, struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_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) +{ + return 0; +} + +static uint32_t bpf_fuse_release_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_release_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_release_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_release_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_releasedir_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_release_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_releasedir_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_release_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_flush_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_flush_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_flush_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_flush_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_lseek_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_lseek_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_lseek_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_lseek_in *in, + struct fuse_lseek_out *out) +{ + return 0; +} + +static uint32_t bpf_fuse_copy_file_range_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_copy_file_range_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_copy_file_range_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_copy_file_range_in *in, + struct fuse_write_out *out) +{ + return 0; +} + +static uint32_t bpf_fuse_fsync_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_fsync_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_fsync_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_fsync_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_dir_fsync_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_fsync_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_dir_fsync_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_fsync_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_getxattr_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_getxattr_in *in, struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_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) +{ + return 0; +} + +static uint32_t bpf_fuse_listxattr_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_getxattr_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_listxattr_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_getxattr_in *in, + struct fuse_buffer *value, struct fuse_getxattr_out *out) +{ + return 0; +} + +static uint32_t bpf_fuse_setxattr_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_setxattr_in *in, struct fuse_buffer *name, + struct fuse_buffer *value) +{ + return 0; +} + +static uint32_t bpf_fuse_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) +{ + return 0; +} + +static uint32_t bpf_fuse_removexattr_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_removexattr_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_read_iter_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_read_iter_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_read_in *in, + struct fuse_read_iter_out *out) +{ + return 0; +} + +static uint32_t bpf_fuse_write_iter_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_write_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_write_iter_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_write_in *in, + struct fuse_write_iter_out *out) +{ + return 0; +} + +static uint32_t bpf_fuse_file_fallocate_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_fallocate_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_file_fallocate_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_fallocate_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_lookup_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_lookup_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name, + struct fuse_entry_out *out, struct fuse_buffer *entries) +{ + return 0; +} + +static uint32_t bpf_fuse_mknod_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_mknod_in *in, struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_mknod_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_mknod_in *in, const struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_mkdir_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_mkdir_in *in, struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_mkdir_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_mkdir_in *in, const struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_rmdir_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_rmdir_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_rename2_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_rename2_in *in, struct fuse_buffer *old_name, + struct fuse_buffer *new_name) +{ + return 0; +} + +static uint32_t bpf_fuse_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) +{ + return 0; +} + +static uint32_t bpf_fuse_rename_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_rename_in *in, struct fuse_buffer *old_name, + struct fuse_buffer *new_name) +{ + return 0; +} + +static uint32_t bpf_fuse_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) +{ + return 0; +} + +static uint32_t bpf_fuse_unlink_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_unlink_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_link_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_link_in *in, struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_link_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_link_in *in, const struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_getattr_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_getattr_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_getattr_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_getattr_in *in, + struct fuse_attr_out *out) +{ + return 0; +} + +static uint32_t bpf_fuse_setattr_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_setattr_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_setattr_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_setattr_in *in, + struct fuse_attr_out *out) +{ + return 0; +} + +static uint32_t bpf_fuse_statfs_prefilter(const struct bpf_fuse_meta_info *meta) +{ + return 0; +} + +static uint32_t bpf_fuse_statfs_postfilter(const struct bpf_fuse_meta_info *meta, + struct fuse_statfs_out *out) +{ + return 0; +} + +static uint32_t bpf_fuse_get_link_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_get_link_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name) +{ + return 0; +} + +static uint32_t bpf_fuse_symlink_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name, struct fuse_buffer *path) +{ + return 0; +} + +static uint32_t bpf_fuse_symlink_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name, const struct fuse_buffer *path) +{ + return 0; +} + +static uint32_t bpf_fuse_readdir_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_readdir_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_read_in *in, + struct fuse_read_out *out, struct fuse_buffer *buffer) +{ + return 0; +} + +static uint32_t bpf_fuse_access_prefilter(const struct bpf_fuse_meta_info *meta, + struct fuse_access_in *in) +{ + return 0; +} + +static uint32_t bpf_fuse_access_postfilter(const struct bpf_fuse_meta_info *meta, + const struct fuse_access_in *in) +{ + return 0; +} + +static struct fuse_ops __bpf_fuse_ops = { + .default_filter = bpf_fuse_default_filter, + .open_prefilter = bpf_fuse_open_prefilter, + .open_postfilter = bpf_fuse_open_postfilter, + .opendir_prefilter = bpf_fuse_opendir_prefilter, + .opendir_postfilter = bpf_fuse_opendir_postfilter, + .create_open_prefilter = bpf_fuse_create_open_prefilter, + .create_open_postfilter = bpf_fuse_create_open_postfilter, + .release_prefilter = bpf_fuse_release_prefilter, + .release_postfilter = bpf_fuse_release_postfilter, + .releasedir_prefilter = bpf_fuse_releasedir_prefilter, + .releasedir_postfilter = bpf_fuse_releasedir_postfilter, + .flush_prefilter = bpf_fuse_flush_prefilter, + .flush_postfilter = bpf_fuse_flush_postfilter, + .lseek_prefilter = bpf_fuse_lseek_prefilter, + .lseek_postfilter = bpf_fuse_lseek_postfilter, + .copy_file_range_prefilter = bpf_fuse_copy_file_range_prefilter, + .copy_file_range_postfilter = bpf_fuse_copy_file_range_postfilter, + .fsync_prefilter = bpf_fuse_fsync_prefilter, + .fsync_postfilter = bpf_fuse_fsync_postfilter, + .dir_fsync_prefilter = bpf_fuse_dir_fsync_prefilter, + .dir_fsync_postfilter = bpf_fuse_dir_fsync_postfilter, + .getxattr_prefilter = bpf_fuse_getxattr_prefilter, + .getxattr_postfilter = bpf_fuse_getxattr_postfilter, + .listxattr_prefilter = bpf_fuse_listxattr_prefilter, + .listxattr_postfilter = bpf_fuse_listxattr_postfilter, + .setxattr_prefilter = bpf_fuse_setxattr_prefilter, + .setxattr_postfilter = bpf_fuse_setxattr_postfilter, + .removexattr_prefilter = bpf_fuse_removexattr_prefilter, + .removexattr_postfilter = bpf_fuse_removexattr_postfilter, + .read_iter_prefilter = bpf_fuse_read_iter_prefilter, + .read_iter_postfilter = bpf_fuse_read_iter_postfilter, + .write_iter_prefilter = bpf_fuse_write_iter_prefilter, + .write_iter_postfilter = bpf_fuse_write_iter_postfilter, + .file_fallocate_prefilter = bpf_fuse_file_fallocate_prefilter, + .file_fallocate_postfilter = bpf_fuse_file_fallocate_postfilter, + .lookup_prefilter = bpf_fuse_lookup_prefilter, + .lookup_postfilter = bpf_fuse_lookup_postfilter, + .mknod_prefilter = bpf_fuse_mknod_prefilter, + .mknod_postfilter = bpf_fuse_mknod_postfilter, + .mkdir_prefilter = bpf_fuse_mkdir_prefilter, + .mkdir_postfilter = bpf_fuse_mkdir_postfilter, + .rmdir_prefilter = bpf_fuse_rmdir_prefilter, + .rmdir_postfilter = bpf_fuse_rmdir_postfilter, + .rename2_prefilter = bpf_fuse_rename2_prefilter, + .rename2_postfilter = bpf_fuse_rename2_postfilter, + .rename_prefilter = bpf_fuse_rename_prefilter, + .rename_postfilter = bpf_fuse_rename_postfilter, + .unlink_prefilter = bpf_fuse_unlink_prefilter, + .unlink_postfilter = bpf_fuse_unlink_postfilter, + .link_prefilter = bpf_fuse_link_prefilter, + .link_postfilter = bpf_fuse_link_postfilter, + .getattr_prefilter = bpf_fuse_getattr_prefilter, + .getattr_postfilter = bpf_fuse_getattr_postfilter, + .setattr_prefilter = bpf_fuse_setattr_prefilter, + .setattr_postfilter = bpf_fuse_setattr_postfilter, + .statfs_prefilter = bpf_fuse_statfs_prefilter, + .statfs_postfilter = bpf_fuse_statfs_postfilter, + .get_link_prefilter = bpf_fuse_get_link_prefilter, + .get_link_postfilter = bpf_fuse_get_link_postfilter, + .symlink_prefilter = bpf_fuse_symlink_prefilter, + .symlink_postfilter = bpf_fuse_symlink_postfilter, + .readdir_prefilter = bpf_fuse_readdir_prefilter, + .readdir_postfilter = bpf_fuse_readdir_postfilter, + .access_prefilter = bpf_fuse_access_prefilter, + .access_postfilter = bpf_fuse_access_postfilter, +}; + +static struct bpf_struct_ops bpf_fuse_ops = { + .verifier_ops = &bpf_fuse_verifier_ops, + .reg = bpf_fuse_reg, + .unreg = bpf_fuse_unreg, + .check_member = bpf_fuse_check_member, + .init_member = bpf_fuse_init_member, + .init = bpf_fuse_init, + .name = "fuse_ops", + .cfi_stubs = &__bpf_fuse_ops, + .owner = THIS_MODULE, +}; + +static int __init bpf_fuse_ops_init(void) +{ + int ret; + + ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, + &bpf_fuse_kfunc_set); + ret = ret ?: register_bpf_struct_ops(&bpf_fuse_ops, fuse_ops); + + return ret; +} +late_initcall(bpf_fuse_ops_init); diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 170d017e8e4a..29f7db457127 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -26,6 +26,7 @@ #include #include #include +#include #include diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index ee86e4d7d5fc..eb0808ed3cd9 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -10947,6 +10947,8 @@ enum special_kfunc_type { KF_bpf_percpu_obj_drop_impl, KF_bpf_throw, KF_bpf_iter_css_task_new, + KF_bpf_fuse_get_rw_dynptr, + KF_bpf_fuse_get_ro_dynptr, }; BTF_SET_START(special_kfunc_set) @@ -10973,6 +10975,8 @@ BTF_ID(func, bpf_throw) #ifdef CONFIG_CGROUPS BTF_ID(func, bpf_iter_css_task_new) #endif +BTF_ID(func, bpf_fuse_get_rw_dynptr) +BTF_ID(func, bpf_fuse_get_ro_dynptr) BTF_SET_END(special_kfunc_set) BTF_ID_LIST(special_kfunc_list) @@ -11003,6 +11007,8 @@ BTF_ID(func, bpf_iter_css_task_new) #else BTF_ID_UNUSED #endif +BTF_ID(func, bpf_fuse_get_rw_dynptr) +BTF_ID(func, bpf_fuse_get_ro_dynptr) static bool is_kfunc_ret_null(struct bpf_kfunc_call_arg_meta *meta) { @@ -11793,7 +11799,9 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ verbose(env, "verifier internal error: missing ref obj id for parent of clone\n"); return -EFAULT; } - } + } else if (meta->func_id == special_kfunc_list[KF_bpf_fuse_get_rw_dynptr] || + meta->func_id == special_kfunc_list[KF_bpf_fuse_get_ro_dynptr]) + dynptr_arg_type |= DYNPTR_TYPE_LOCAL; ret = process_dynptr_func(env, regno, insn_idx, dynptr_arg_type, clone_ref_obj_id); if (ret < 0) From patchwork Fri Mar 29 01:53:42 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609903 X-Patchwork-Delegate: bpf@iogearbox.net Received: from mail-yw1-f202.google.com (mail-yw1-f202.google.com [209.85.128.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ADC148562E for ; Fri, 29 Mar 2024 01:55:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677303; cv=none; b=OSLuOZFxSuDMov0WVaazaaQ9PD+AhStRu4UQt5ZFM9nMmcOCKF8CdCXjfH9ceeyG+LdBGW5wOOUBzRhbIgyLuxbTnA9IyGLCacXJuwhW5X75XYgMO8dfPOoNnTSm8nucNJ/bIN8ERf1o4pU7lvleuGTl4AyHz4Xt7TGFfTZYE5o= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677303; c=relaxed/simple; bh=KCoIZNUHcwv2RV9ZRbHBJii4HaQ9xYYtK2VIovQhTY4=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=Twca8Avs3z9gUb1Q7OfAE1qJzWb21/wud82qy224Zu2JP19ftTs8Aw/XlekdY7NxFFxvolfiBd44SvyNLc1sh7U1Wr0IYXpSGQX6gRzNH1uGVsF4Z4GUjrJy8F5y67jmJYiFWXtURrzmLQBC22MoSsh7Elp+QZgLYVld3abP5pM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=cGvlUFtl; arc=none smtp.client-ip=209.85.128.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="cGvlUFtl" Received: by mail-yw1-f202.google.com with SMTP id 00721157ae682-60a55cd262aso26562957b3.2 for ; Thu, 28 Mar 2024 18:55:01 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677301; x=1712282101; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=/G940zvh+KpSLBvufKEbVa5uESG7TAs31JyWujh5JiA=; b=cGvlUFtlWGRTPkcyYNCMwvSxvP8qlwrZr3JOFvlMOAHhswasUvQzmPZLlH01AY8Kke CDnQgoMEJhPRIKJsyS04oYc8Uuu55Dzdh5jmQyxNhfsUDMZZEkM1xiv1h5HuuzYlN6zl PODUMjxw8QyB8/DxnT+YoKR3DI/rJSNob2BNPMMEmg+Bogh048li815kIsg18b85Utld Iymm98q4jMGrSCjfKBjmefZPnJZpcMtoplmG+B4yi7PxdPAoPOD9gEgZgZCkQSQESDHb zNE7xCcmRxlLfrm4sMPXWZKy3jJxhIq7sq9SCl2/E0a/qS5mSG6DsrzM9EQEaQppU9Hb 4vfA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677301; x=1712282101; 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=/G940zvh+KpSLBvufKEbVa5uESG7TAs31JyWujh5JiA=; b=N67ypBkRrX1H2oNOl57YKS5Ma5igInH4B2UJm7RQyJDgBigI/IIQzKJxsP8yM/gMTB O0LKp439gYU4e0lNoDnjeBhqetCquNE7IKxfW6PbjgUpGFVXfCvwTrHXF68Hdl1DBRKd wZGUD5tEoSSVN9OKwG+KFPldPbuXhwXQwTOkzJttzVCEEiZ/wUmVERkFWZFtou6BuCe/ t20jQiqGfqXGBWGNiFMOs2eJ1OilwdpnwMHjeTSoVXThf9fHJBWaSf7Rh6qvTxo5TJgW TLnRF7LtNsXLZ9YvcPKsNtFdBMCQVvslKzJpVjTieWAa/BU6fQ+J4zrvMrF9EJwiEzOC nSHA== X-Forwarded-Encrypted: i=1; AJvYcCXlzVXItDPT815n4Cmv+MvrxOIPuRKBhrJRMMxNJxyBuOyWYIlN29+OuRHz/oKLD/oC2Ujk8rr3ecjidRatcxm941gn X-Gm-Message-State: AOJu0YxUHMW9WqYXisiwFbd5VMg+QAtSNHCq3ompi/vHO4RDVkh84MKA ejBD0nMfhBisJUVK+3SLn+aeVIj8q692xpoOSlo5tZg7+0Plvl5WzvNK7zO+DtlfdbSgKhcprkC ioA== X-Google-Smtp-Source: AGHT+IGsCzOtwN105f/bltaErVUfpSBRC3F9RkDjk/0Wfd4wmn+phWiKkcXdiSoxa6ZQkSCXtl1Jd331NFw= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a0d:d8c5:0:b0:614:4c1:c8d with SMTP id a188-20020a0dd8c5000000b0061404c10c8dmr308593ywe.6.1711677300998; Thu, 28 Mar 2024 18:55:00 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:42 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-28-drosen@google.com> Subject: [RFC PATCH v4 27/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-Delegate: bpf@iogearbox.net X-Patchwork-State: RFC 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 43356faaa057..ae76b99c07c1 100644 --- a/kernel/bpf/bpf_struct_ops.c +++ b/kernel/bpf/bpf_struct_ops.c @@ -1024,6 +1024,7 @@ bool bpf_struct_ops_get(const void *kdata) map = __bpf_map_inc_not_zero(&st_map->map, false); return !IS_ERR(map); } +EXPORT_SYMBOL_GPL(bpf_struct_ops_get); void bpf_struct_ops_put(const void *kdata) { @@ -1035,6 +1036,7 @@ void bpf_struct_ops_put(const void *kdata) bpf_map_put(&st_map->map); } +EXPORT_SYMBOL_GPL(bpf_struct_ops_put); static bool bpf_struct_ops_valid_to_reg(struct bpf_map *map) { From patchwork Fri Mar 29 01:53:43 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609905 X-Patchwork-Delegate: bpf@iogearbox.net Received: from mail-yb1-f202.google.com (mail-yb1-f202.google.com [209.85.219.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2CC668564A for ; Fri, 29 Mar 2024 01:55:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677305; cv=none; b=otVF0YiuEn9XXUyRfLbVs+Fxlefh7Ba5tCzRSgdRStS6LlCapSaH5VqUCVXG3mTNSNfld3iCPglcEEvgxHZH/SZGbXSIqfWFKBuYk1fk1LP+fhk1Vfd0EcN6MC2rkftfl5kPW+caykNNS03ydlnZRPneo57uyNt/w3VPXct6hrQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677305; c=relaxed/simple; bh=fRKdI3QiwIiUODgq+UYwDm6APftE3YO7UTQiQdpsLQk=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=lg5F1Dc7gVZnAxoj5Hc6F8Pe7HudeL4xwUn8ztAbYZBtfwVE/ht2yxufOqZySo9Hmt433Q0psfLsL2MmdHJ/vvCC5/AQbiaUAgPLMWDeivoxKRLzuNKCU/9Sd6x6CjO+02gFXgH3QWYEqwT9k/aDGr3GGMkG2TNgU1nPVz1bkSs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=qPLeyrQ5; arc=none smtp.client-ip=209.85.219.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="qPLeyrQ5" Received: by mail-yb1-f202.google.com with SMTP id 3f1490d57ef6-dced704f17cso2711899276.1 for ; Thu, 28 Mar 2024 18:55:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677303; x=1712282103; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=zEN/hYQfa73u/9gIRdrmlwDPst/lwM0QAOXzH39EmOE=; b=qPLeyrQ5TH2DAWgV8I4AWYGcmgKMHTjMKC076tbFiK7GjGM9ftByjuMhqgyn3rmnQG DIbIRf08mveERGW5T+C8jOy7SRdJYgi018cEABgOnQldlk55sZ8YoWpr8yjMg6aCZXSo gK/Z1qcWud+JvCpPrf0B+6RSXx0792sRyHVfFUZ2q4ZqfVJ4KZaDgc6Fr7d8NNUPfAof iByM1i2xzaY2k/sDeZKOPJoKcPF71FHXoFgZT8T1u/7Tds1vfLBRV8v8FH0km54S2t9m 0nY+Ne/84wuU5tKpqLGygFLazM5UD8kZPj5Jfzj5CVz79OhlDhUYi+C01KEoV433bMEi Ni8Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677303; x=1712282103; 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=zEN/hYQfa73u/9gIRdrmlwDPst/lwM0QAOXzH39EmOE=; b=URSMCT/q75d4OKS9pNS/U055OynTUPQeX0I3hALVE1V3IdwQypWEGhXoRzXx2JRiw+ vt53N7IWH/ltPbJ0w0hj+dIbgleHgPI0loZE9rxxV0bwVvhnnqmXiMHQp+3GAteLi8AC zpVuvG4N6lPvZrdWrtdQHdTuNOOYNBInK1Zz9L5ejOA8hYUnOVCnwL/PRdGsBU/TqH+4 PL9twZnOFFIrPDDWdbz0nPQ58B/2ZLLSGUw4CwhxQm4mDpqz7FDPSabuERQCJFGaulu6 19tV3dEeee27bJppJuokAkvJ68Ey0KPYcPNH8ZmmHEauEsCfgixssi3eSK+TGoiE2SE1 jf0A== X-Forwarded-Encrypted: i=1; AJvYcCUe8NCHhwOAN+laTTDu8l52dN1nFUQJMa6RViRvTtiKXJMUdrbNY6S9N9uFD8JRR6sulytDFuDlhicRFS6s1bR0GVsG X-Gm-Message-State: AOJu0YysOty4W+ZJp/W8LuQzIp3t6bzeVQfegFsLw1Y3hYs5Teka74uz H+ZR/YKtXFobZ7mXU9leExeeo1uvR91yXPrQV2uw35QLIiUg3vn304EnShQmZvVouf2hhHztkCL OqA== X-Google-Smtp-Source: AGHT+IGJkcoNjDwu0yATYCHjKDKZoR5yIciZSAATbPvRVnUKhEVFpM8/s5FsZmz4KVaPxicOk3AftJZTH7I= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:2481:b0:dd9:2a64:e98a with SMTP id ds1-20020a056902248100b00dd92a64e98amr86515ybb.9.1711677302942; Thu, 28 Mar 2024 18:55:02 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:43 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-29-drosen@google.com> Subject: [RFC PATCH v4 28/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-Delegate: bpf@iogearbox.net X-Patchwork-State: RFC 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 | 207 +++++++++++++++++++++++++++++++++++++++ fs/fuse/fuse_i.h | 26 +++++ include/linux/bpf_fuse.h | 8 ++ 5 files changed, 244 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) += virtiofs.o fuse-y := dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o fuse-$(CONFIG_FUSE_DAX) += dax.o -fuse-$(CONFIG_FUSE_BPF) += backing.o +fuse-$(CONFIG_FUSE_BPF) += backing.o bpf_register.o virtiofs-y := virtio_fs.o diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 884c690becd5..d5fcaef8e6b5 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -3392,6 +3392,7 @@ int fuse_bpf_access(int *out, struct inode *inode, int mask) int __init fuse_bpf_init(void) { + init_fuse_bpf(); fuse_bpf_aio_request_cachep = kmem_cache_create("fuse_bpf_aio_req", sizeof(struct fuse_bpf_aio_req), 0, SLAB_HWCACHE_ALIGN, NULL); @@ -3403,5 +3404,6 @@ int __init fuse_bpf_init(void) 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..32f96004b161 --- /dev/null +++ b/fs/fuse/bpf_register.c @@ -0,0 +1,207 @@ +// 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 = { 0 }; +struct fuse_ops *tmp_f_op = &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 char *name, unsigned int len) +{ + unsigned long hash = init_name_hash(salt); + + while (len--) + hash = partial_name_hash(tolower(*name++), hash); + return end_name_hash(hash); +} + +static inline void qstr_init(struct qstr *q, const char *name) +{ + q->name = name; + q->len = strlen(q->name); + q->hash = full_name_case_hash(0, q->name, q->len); +} + +static inline int qstr_copy(const struct qstr *src, struct qstr *dest) +{ + dest->name = kstrdup(src->name, GFP_KERNEL); + dest->hash_len = 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 = s1->len == s2->len; + r2 = s1->hash == s2->hash; + r3 = memcmp(s1->name, s2->name, s1->len); + res = (s1->len == s2->len && s1->hash == 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 = 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 = hash_cur->ops; + ret_ops = 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 *key, + struct fuse_ops *value) +{ + struct hashtable_entry *ret = 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 = 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 = 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 = 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 = __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 = 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; + + 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); + pr_info("fuse: destroyed fuse_op list\n"); +} + +static struct bpf_fuse_ops_attach bpf_fuse_ops_connect = { + .fuse_register_bpf = ®ister_fuse_op, + .fuse_unregister_bpf = &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 f1a8f8a97f1f..082cfd14de53 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1423,6 +1423,32 @@ void fuse_file_release(struct inode *inode, struct fuse_file *ff, unsigned int open_flags, fl_owner_t id, bool isdir); /* 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 enum fuse_bpf_set { FUSE_BPF_UNCHANGED = 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]; }; +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 */ From patchwork Fri Mar 29 01:53:44 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609906 Received: from mail-yw1-f201.google.com (mail-yw1-f201.google.com [209.85.128.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 38FE185C66 for ; Fri, 29 Mar 2024 01:55:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677309; cv=none; b=HoHsYSoovnh/LEtqvl6EaY7DaW8jyS506f4TbjxNV09844z4l3wRuCa7md5AdqZCH4hVcxf6+G9WydkbNSMN7/zBGSWo13fD/kxQ2kUW4QK3XVGJJbzHxr7b9Y7hVdypbvszfWKjb+BeQkwTVGX11NoOroSz1M0KXG1ZfcjkYG8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677309; c=relaxed/simple; bh=WB2+aIBBnGAmjt8YgnuqmpzErACdvxw+HOIgdgWZMeE=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=kGDEcpRhFrL5w2FDnVBhWq6nStoQYR2dgB5kp2Rc8id3l5gW1sa70ShYJy8dm3xck2Er3c+Dm+HTugZE9iyAUpxzW2v+IsnwJQxsZaZP3YFafv81XnwlzT0PY1xOQ9Mz00bJlBlWR9Wqu4m8QxGKqCRjpm5CDf5/qI6HGf1K9x8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=uUPv+h9A; arc=none smtp.client-ip=209.85.128.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="uUPv+h9A" Received: by mail-yw1-f201.google.com with SMTP id 00721157ae682-6134cfcacdcso26854607b3.2 for ; Thu, 28 Mar 2024 18:55:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677305; x=1712282105; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=yIGNuIERprRFbYtu59mkQ7iO+y89Tk9z1OQnfjfFmpQ=; b=uUPv+h9At/DZmmUS5UHauSMsxolAYT2I4HmesATKLo33m3H3/lFPEB5WcYldth6M/i 5eUaZ0eEsBfqVyZkd2KY0nfRzAXhajb8DVJmNg86c+EBoAgol9nv5XOzovNvaOT3wZhu XCYD13/JLXBhicIy6xURmLR3gVuUr9qNaXZ53+aFCza5zBmzZK2pCISyn0SG9ZYgxG3I kk8VklGsuFs0mnM+eZyIbu+q0H9nMURPn5yl6I9ke+LCfpnq8h89LggvDq8/r666Jt2c 2ZxU7wQ82r5WWsnNrkkJuj+lFn6CQdoStwh/t+xQZKQLe87CNxjtO8kJ38BiOEiWKviB UiOA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677305; x=1712282105; 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=yIGNuIERprRFbYtu59mkQ7iO+y89Tk9z1OQnfjfFmpQ=; b=gPQDmg2yG5uW/cy6W/ZrwMK6tVkgSAtDWQN4+jx2oBcA7qQpAs56yBZzByngmXVMNY BVMKvdneKGs/ri8aO/f1YDvbJRj/bkE56W3V7cB8qiFsOQHrNS0KfUOBn48+wjsUQiE/ 7j3XqUr88j5rwrxYaNUJvHWD8Iao6a1/+josO63VhETkopKQzaSrJnR3Fywe2IfTuNfV 9nd6xuqQfptVd1I3LzhTTJ2AUz2GK3JVVGvL6vtZ9MJnqAZezWb62oaEqz9GnjJDs7fD CxHjKdvAikt/fzJ60oculsJa4xTwuw/GFg4AtaALRkYHLuvthnFR7lvbjB3mVUPdGC3E Z22g== X-Forwarded-Encrypted: i=1; AJvYcCXlluKcEgI8oxJPtfhV0cA4jho0SyGUPKgNt/vYMAgJSYuiZ07IekB4ghm7TAVDa2roWLmdrcgHWTMPXAJDMw1EWGek X-Gm-Message-State: AOJu0YxaH9VwO7Ka4PaMh/hfGd1bSKyf5y81OyIIzsM2gUkOW0dyl5NT fWHvmf9wg7+cR17JPF2dS6ksh9jcNLt1IoHLUDuT39YBbM3wcu9mBVgnMMzzps+x0U4syKeVa3O Okg== X-Google-Smtp-Source: AGHT+IGkSDFcSAQR5ALwwIQ3JXRpP9Dx6/nMJka01ohuszHNcmHsTHrY0ZAI1RYhTHOqdHZpZX4PSnyy7Pg= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a81:6f86:0:b0:611:2af3:8e07 with SMTP id k128-20020a816f86000000b006112af38e07mr308268ywc.3.1711677305218; Thu, 28 Mar 2024 18:55:05 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:44 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-30-drosen@google.com> Subject: [RFC PATCH v4 29/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-State: RFC 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 | 100 +++++++++++++++++++++++++++++++++++++++++----- fs/fuse/dir.c | 19 ++++++++- fs/fuse/fuse_i.h | 12 ++++++ fs/fuse/inode.c | 45 ++++++++++++++++++--- 4 files changed, 160 insertions(+), 16 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index d5fcaef8e6b5..5fdca3a6183f 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -6,6 +6,7 @@ #include "fuse_i.h" +#include #include #include #include @@ -176,12 +177,13 @@ static void fuse_get_backing_path(struct file *file, struct path *path) static bool has_file(int type) { - return type == FUSE_ENTRY_BACKING; + return (type == FUSE_ENTRY_BACKING); } /* - * 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 particular + * lookup. These are inherited by default. A Bpf requires a backing file to be + * meaningful. * * In the future, we may support multiple bpfs, and multiple backing files for * the bpf to choose between. @@ -190,14 +192,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 = false; + bool has_bpf_ops = false; int num_entries; int err = -EINVAL; int i; @@ -235,6 +237,11 @@ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe, int num) goto out_err; fbe->backing_action = FUSE_BPF_REMOVE; break; + case FUSE_ENTRY_REMOVE_BPF: + if (fbe->bpf_action || i == 2) + goto out_err; + fbe->bpf_action = FUSE_BPF_REMOVE; + break; case FUSE_ENTRY_BACKING: if (fbe->backing_action) goto out_err; @@ -242,8 +249,17 @@ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe, int num) fbe->backing_action = FUSE_BPF_SET; has_backing = true; break; + case FUSE_ENTRY_BPF: + if (fbe->bpf_action || i == 2) + goto out_err; + ops = find_fuse_ops(fbeo->name); + if (!ops) + goto out_err; + has_bpf_ops = true; + fbe->bpf_action = FUSE_BPF_SET; + fbe->ops = ops; + break; default: - err = -EINVAL; goto out_err; } if (has_file(fbeo->entry_type)) { @@ -260,6 +276,10 @@ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe, int num) fput(file); if (has_backing) path_put_init(&fbe->backing_path); + if (has_bpf_ops) { + put_fuse_ops(fbe->ops); + fbe->ops = NULL; + } return err; } @@ -538,6 +558,15 @@ static int fuse_create_open_backing(struct bpf_fuse_args *fa, int *out, goto out; } + if (get_fuse_inode(inode)->bpf_ops) + put_fuse_ops(get_fuse_inode(inode)->bpf_ops); + get_fuse_inode(inode)->bpf_ops = dir_fuse_inode->bpf_ops; + if (get_fuse_inode(inode)->bpf_ops) + if (!get_fuse_ops(get_fuse_inode(inode)->bpf_ops)) { + *out = -EINVAL; + goto out; + } + newent = d_splice_alias(inode, entry); if (IS_ERR(newent)) { *out = PTR_ERR(newent); @@ -1858,12 +1887,56 @@ int fuse_handle_backing(struct fuse_bpf_entry *fbe, struct path *backing_path) return 0; } +int fuse_handle_bpf_ops(struct fuse_bpf_entry *fbe, struct inode *parent, + struct fuse_ops **ops) +{ + struct fuse_ops *new_ops = NULL; + + switch (fbe->bpf_action) { + case FUSE_BPF_UNCHANGED: { + /* Parent isn't presented, but we want to keep + * Don't touch bpf program at all in this case + */ + if (!parent) + return 0; + + new_ops = get_fuse_inode(parent)->bpf_ops; + if (new_ops && !get_fuse_ops(new_ops)) + return -EINVAL; + break; + } + + case FUSE_BPF_REMOVE: + break; + + case FUSE_BPF_SET: + new_ops = fbe->ops; + + if (!new_ops) + return -EINVAL; + break; + + default: + return -EINVAL; + } + + /* Cannot change existing program */ + if (*ops) { + if (new_ops) + put_fuse_ops(new_ops); + return new_ops == *ops ? 0 : -EINVAL; + } + + *ops = 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) { struct fuse_dentry *fd; struct dentry *backing_dentry; - struct inode *inode, *backing_inode; + struct inode *inode = NULL, *backing_inode; struct inode *d_inode = entry->d_inode; struct fuse_entry_out *feo = fa->out_args[0].value; struct fuse_bpf_entry_out *febo = fa->out_args[1].buffer->data; @@ -1891,13 +1964,22 @@ static int fuse_lookup_finalize(struct bpf_fuse_args *fa, struct dentry **out, target_nodeid = get_fuse_inode(d_inode)->nodeid; inode = fuse_iget_backing(dir->i_sb, target_nodeid, backing_inode); + if (!inode) + return -EIO; - if (IS_ERR(inode)) - return PTR_ERR(inode); + error = fuse_handle_bpf_ops(fbe, dir, &get_fuse_inode(inode)->bpf_ops); + if (error) { + return error; + goto out; + } get_fuse_inode(inode)->nodeid = feo->nodeid; *out = d_splice_alias(inode, entry); + if (!IS_ERR(*out)) + inode = NULL; +out: + iput(inode); return 0; } diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 08f93a981665..f5ab3b79b3cc 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -202,6 +203,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 = NULL; int err; bool ret = true; @@ -216,9 +218,15 @@ static bool backing_data_changed(struct fuse_inode *fi, struct dentry *entry, if (err) goto put_inode; - ret = (fi->backing_inode != new_backing_inode || - !path_equal(&get_fuse_dentry(entry)->backing_path, &new_backing_path)); + err = fuse_handle_bpf_ops(bpf_arg, entry->d_parent->d_inode, &ops); + if (err) + goto put_bpf; + ret = (ops != fi->bpf_ops || fi->backing_inode != 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; @@ -486,6 +494,13 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, *inode = fuse_iget_backing(sb, outarg->nodeid, backing_inode); if (!*inode) goto out_queue_forget; + + err = fuse_handle_bpf_ops(&bpf_arg, NULL, &get_fuse_inode(*inode)->bpf_ops); + if (err) { + iput(*inode); + *inode = NULL; + goto out_put_forget; + } } else #endif { diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 082cfd14de53..665673d91753 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -33,6 +33,7 @@ #include #include #include +#include #include /** Default max number of pages that can be used in a single read request */ @@ -122,6 +123,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 /** Unique ID, which identifies the inode between userspace @@ -591,6 +598,7 @@ struct fuse_fs_context { unsigned int max_read; unsigned int blksize; const char *subtype; + struct fuse_ops *root_ops; struct file *root_dir; /* DAX device, may be NULL */ @@ -1461,6 +1469,8 @@ struct fuse_bpf_entry { enum fuse_bpf_set backing_action; struct path backing_path; + enum fuse_bpf_set bpf_action; + struct fuse_ops *ops; bool is_used; }; @@ -1687,6 +1697,8 @@ int fuse_file_flock_backing(struct file *file, int cmd, struct file_lock *fl); 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_path); +int fuse_handle_bpf_ops(struct fuse_bpf_entry *fbe, struct inode *parent, + struct fuse_ops **ops); int fuse_revalidate_backing(struct dentry *entry, unsigned int flags); diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index b4332416e23a..67d70b3c4abb 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -98,6 +98,7 @@ static struct inode *fuse_alloc_inode(struct super_block *sb) fi->inval_mask = ~0; #ifdef CONFIG_FUSE_BPF fi->backing_inode = NULL; + fi->bpf_ops = NULL; #endif fi->nodeid = 0; fi->nlookup = 0; @@ -131,6 +132,10 @@ static void fuse_free_inode(struct inode *inode) kfree(fi->forget); #ifdef CONFIG_FUSE_DAX kfree(fi->dax); +#endif +#ifdef CONFIG_FUSE_BPF + if (fi->bpf_ops) + put_fuse_ops(fi->bpf_ops); #endif kmem_cache_free(fuse_inode_cachep, fi); } @@ -153,10 +158,6 @@ 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); -#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) { @@ -181,6 +182,15 @@ static void fuse_evict_inode(struct inode *inode) } } +#ifdef CONFIG_FUSE_BPF +static void fuse_destroy_inode(struct inode *inode) +{ + struct fuse_inode *fi = get_fuse_inode(inode); + + iput(fi->backing_inode); +} +#endif + static int fuse_reconfigure(struct fs_context *fsc) { struct super_block *sb = fsc->root->d_sb; @@ -828,6 +838,7 @@ enum { OPT_ALLOW_OTHER, OPT_MAX_READ, OPT_BLKSIZE, + OPT_ROOT_BPF, OPT_ROOT_DIR, OPT_NO_DAEMON, OPT_ERR @@ -844,6 +855,7 @@ static const struct fs_parameter_spec fuse_fs_parameters[] = { 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), {} @@ -929,6 +941,18 @@ static int fuse_parse_param(struct fs_context *fsc, struct fs_parameter *param) ctx->blksize = result.uint_32; break; + 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_FUSE_NAME_MAX); + } + + ctx->root_ops = find_fuse_ops(param->string); + if (IS_ERR_OR_NULL(ctx->root_ops)) { + ctx->root_ops = NULL; + return invalfc(fsc, "Unable to find bpf program"); + } + break; + case OPT_ROOT_DIR: ctx->root_dir = fget(result.uint_32); if (!ctx->root_dir) @@ -954,6 +978,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); } @@ -1090,6 +1116,7 @@ EXPORT_SYMBOL_GPL(fuse_conn_get); 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; @@ -1104,6 +1131,10 @@ static struct inode *fuse_get_root_inode(struct super_block *sb, return NULL; #ifdef CONFIG_FUSE_BPF + get_fuse_inode(inode)->bpf_ops = root_bpf_ops; + if (root_bpf_ops) + get_fuse_ops(root_bpf_ops); + if (backing_fd) { get_fuse_inode(inode)->backing_inode = backing_fd->f_inode; ihold(backing_fd->f_inode); @@ -1274,6 +1305,9 @@ static const struct export_operations fuse_export_operations = { static const struct super_operations fuse_super_operations = { .alloc_inode = fuse_alloc_inode, +#ifdef CONFIG_FUSE_BPF + .destroy_inode = fuse_destroy_inode, +#endif .free_inode = fuse_free_inode, .evict_inode = fuse_evict_inode, .write_inode = fuse_write_inode, @@ -1808,7 +1842,8 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx) fc->no_daemon = ctx->no_daemon; err = -ENOMEM; - root = fuse_get_root_inode(sb, ctx->rootmode, ctx->root_dir); + root = fuse_get_root_inode(sb, ctx->rootmode, ctx->root_ops, + ctx->root_dir); sb->s_d_op = &fuse_root_dentry_operations; root_dentry = d_make_root(root); if (!root_dentry) From patchwork Fri Mar 29 01:53:45 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609907 Received: from mail-yb1-f201.google.com (mail-yb1-f201.google.com [209.85.219.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 204A912D1FC for ; Fri, 29 Mar 2024 01:55:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677311; cv=none; b=eveV/Lics8Ec5BCQVznuFOZ2az2+QWxb7EUZqwEyChKUgvqCxIA3js37CPKo79S8jdWqEtAOX/iJrhi2oM9+fu6LCV4Z4sxqOIuGrANkoJnGRLkZmBMw9cKZGNVd088B8eXsr8kB5bRLTClbBayA7qmkWZyqJvAFcFk6tawDzf8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677311; c=relaxed/simple; bh=CgMsFqBaCcPqqw46vH6wtUPZlA/+0MGfbfw/YwE9cX4=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=bLLFmEqwP1GxK4JtFL33Pn3b4BY0GwsARvUj7e8qXWwYeonDkbVriTm+O74bHeOeLiU2JDx8S8EwZBzn0P9DbJhZlOmjTcfykrw2iTgWtKZZeTch1LQO5plzI1eDHxfJcJd0T0Vg7OuGrQ85QBHsduthWc0cekZP1pNfr+xwGME= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=1JOOxwOG; arc=none smtp.client-ip=209.85.219.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="1JOOxwOG" Received: by mail-yb1-f201.google.com with SMTP id 3f1490d57ef6-dbf216080f5so2549090276.1 for ; Thu, 28 Mar 2024 18:55:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677307; x=1712282107; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=QmZBe/ZwvSJip27C2Q1EQInDFuwBtY57Kmn/D18ftkk=; b=1JOOxwOGmCswQ1zPX5h6mEYIyUi0LLAe2SjTEY4f+p7PYKSE6C7TROlf6F1MFyUKPp R792yKL7iGwB8XAlGeFqMRHxYEejIO4I9sdd/hsoHY4xsTU4lMHLZnXLNWwi3Od40x/T HiYceUfMY9YKmBmjyE1zixrzAtIZDc86POjxOKYO544BzOKOEdZxlLbx2+d0zHKRGtIN Bfz5on4mkV+/lwZJOaxsxfJ79oC+0FP2vEr44v0f+U2DjTsU1/BWkkmTGyWsZiCYDBN+ RV7XmgbVD8+TPrwXqMlabkfJ+zEovyLjzB9n1JXQsTnBtzLIVq00t6EBzsGz0KGijvNi knwA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677307; x=1712282107; 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=QmZBe/ZwvSJip27C2Q1EQInDFuwBtY57Kmn/D18ftkk=; b=dosmagyg/Sa/QCtQhSjZ/7B1Hgo0SdNqlrdMTf+bvqvGGrvTpe3mP2Pty0FXYgcbO1 RLiVQt4pzlU/+fwwKZFX3iUjz+JCWz1CIhA+exu9L8bcToQ43vjy0Pn9vnwNRsWICRTp mn8RzQ8Z8qZAvUf+4jbZSvKDSScWfTbSVs65dmrWezuACRg8hVxcFXT6h07PPhYAqQnW PmqCtbuIrZAB9mgHwUU4WRsOJf/aB8zXES515DwDrQYzIjDkeO8MYCtQc/MrvkVsMVS8 1yEwYH321xhYGFNDlPlINwLqLG8QNkiXLC+szWetSk7KVoc0pfJ2Cd2kEOWhmQjp5keO CkWw== X-Forwarded-Encrypted: i=1; AJvYcCWtBWOPWE8f3uhW2yda2zSVaLNkkK5vAeih+wfQ1wb7YkNMlrw6D1izFWUfjCwJkBxEUJQXdaOHN+60U0UYnFxRIbFD X-Gm-Message-State: AOJu0YyI5wKYva9aw1QPaH1JVYs5Bse4s2Rwkukhqi6iqLv375gDm7Y9 392g0MiutPr7k00DXCMNX6Vh2KQcP1Y0ubnsyixW3Sj/Kyp2+VsNENYP6R/hA/H13tjAwTJfV5R PDQ== X-Google-Smtp-Source: AGHT+IFKlbxBZjVIpQT5GlWWV6NR8dVc2cjvCgz515bGDKLFtMAqlTVCikvr3ohwlI6czc44ldz9lR599hs= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:250b:b0:dcc:54d0:85e0 with SMTP id dt11-20020a056902250b00b00dcc54d085e0mr309483ybb.11.1711677307400; Thu, 28 Mar 2024 18:55:07 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:45 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-31-drosen@google.com> Subject: [RFC PATCH v4 30/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-State: RFC 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 | 605 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 605 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 5fdca3a6183f..8172144e557d 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -16,6 +16,27 @@ #include #include +static inline void bpf_fuse_set_in_immutable(struct bpf_fuse_args *fa) +{ + int i; + + for (i = 0; i < FUSE_MAX_ARGS_IN; i++) + if (fa->in_args[i].is_buffer) + fa->in_args[i].buffer->flags |= BPF_FUSE_IMMUTABLE; +} + +static inline void bpf_fuse_free_alloced(struct bpf_fuse_args *fa) +{ + int i; + + for (i = 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 = 0; i < FUSE_MAX_ARGS_OUT; i++) + if (fa->out_args[i].is_buffer && (fa->out_args[i].buffer->flags & BPF_FUSE_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 @@ -25,6 +46,10 @@ * up fa and io based on args * void initialize_out(struct bpf_fuse_args *fa, io *in_out, args...): function that sets * up fa and io based on args + * int call_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, 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 *meta, 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 @@ -32,13 +57,16 @@ */ #define bpf_fuse_backing(inode, io, out, \ initialize_in, initialize_out, \ + call_prefilter, call_postfilter, \ backing, finalize, args...) \ ({ \ struct fuse_inode *fuse_inode = get_fuse_inode(inode); \ + struct fuse_ops *fuse_ops = fuse_inode->bpf_ops; \ struct bpf_fuse_args fa = { 0 }; \ bool initialized = false; \ bool handled = false; \ ssize_t res; \ + int bpf_next; \ io feo = { 0 }; \ int error = 0; \ \ @@ -51,16 +79,46 @@ if (error) \ break; \ \ + fa.info.opcode |= FUSE_PREFILTER; \ + if (fuse_ops) \ + bpf_next = call_prefilter(fuse_ops, \ + &fa.info, &feo); \ + else \ + bpf_next = BPF_FUSE_CONTINUE; \ + if (bpf_next < 0) { \ + error = bpf_next; \ + break; \ + } \ + \ + bpf_fuse_set_in_immutable(&fa); \ + \ error = initialize_out(&fa, &feo, args); \ if (error) \ break; \ \ initialized = true; \ + if (bpf_next == BPF_FUSE_USER) { \ + handled = false; \ + break; \ + } \ + \ + fa.info.opcode &= ~FUSE_PREFILTER; \ \ error = backing(&fa, out, args); \ if (error < 0) \ fa.info.error_in = error; \ \ + if (bpf_next == BPF_FUSE_CONTINUE) \ + break; \ + \ + fa.info.opcode |= FUSE_POSTFILTER; \ + if (bpf_next == BPF_FUSE_POSTFILTER) \ + bpf_next = call_postfilter(fuse_ops, &fa.info, &feo);\ + if (bpf_next < 0) { \ + error = bpf_next; \ + break; \ + } \ + \ } while (false); \ \ if (initialized && handled) { \ @@ -68,6 +126,7 @@ if (res) \ error = res; \ } \ + bpf_fuse_free_alloced(&fa); \ \ *out = error ? _Generic((*out), \ default : \ @@ -362,6 +421,34 @@ static int fuse_open_initialize_out(struct bpf_fuse_args *fa, struct fuse_open_a return 0; } +static int fuse_open_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, + struct fuse_open_args *open) +{ + if (meta->opcode == (FUSE_OPEN | FUSE_PREFILTER)) { + if (ops->open_prefilter) + return ops->open_prefilter(meta, &open->in); + } + if (meta->opcode == (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 == (FUSE_OPEN | FUSE_POSTFILTER)) { + if (ops->open_postfilter) + return ops->open_postfilter(meta, &open->in, &open->out); + } + if (meta->opcode == (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) { @@ -430,6 +517,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); } @@ -495,6 +583,22 @@ static int fuse_create_open_initialize_out(struct bpf_fuse_args *fa, struct fuse return 0; } +static int fuse_create_open_prefilter(struct fuse_ops *ops, struct bpf_fuse_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_fuse_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 = get_fuse_mount(inode); @@ -599,12 +703,16 @@ static int fuse_create_open_finalize(struct bpf_fuse_args *fa, int *out, 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_prefilter, + fuse_create_open_postfilter, fuse_create_open_backing, fuse_create_open_finalize, dir, entry, file, flags, mode); @@ -639,6 +747,38 @@ static int fuse_release_initialize_out(struct bpf_fuse_args *fa, struct fuse_rel return 0; } +static int fuse_release_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_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_meta_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_fuse_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_release_backing(struct bpf_fuse_args *fa, int *out, struct inode *inode, struct fuse_file *ff) { @@ -656,6 +796,7 @@ int fuse_bpf_release(int *out, struct inode *inode, struct fuse_file *ff) { 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, ff); } @@ -664,6 +805,7 @@ int fuse_bpf_releasedir(int *out, struct inode *inode, struct fuse_file *ff) { return bpf_fuse_backing(inode, struct fuse_release_in, out, fuse_release_initialize_in, fuse_release_initialize_out, + fuse_releasedir_prefilter, fuse_releasedir_postfilter, fuse_release_backing, fuse_release_finalize, inode, ff); } @@ -696,6 +838,22 @@ static int fuse_flush_initialize_out(struct bpf_fuse_args *fa, struct fuse_flush return 0; } +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_meta_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 file *file, fl_owner_t id) { struct fuse_file *fuse_file = file->private_data; @@ -716,6 +874,7 @@ int fuse_bpf_flush(int *out, struct inode *inode, struct 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); } @@ -759,6 +918,22 @@ static int fuse_lseek_initialize_out(struct bpf_fuse_args *fa, struct fuse_lseek return 0; } +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_meta_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) { @@ -805,6 +980,7 @@ int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct 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); } @@ -857,6 +1033,22 @@ static int fuse_copy_file_range_initialize_out(struct bpf_fuse_args *fa, return 0; } +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 bpf_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) @@ -890,6 +1082,8 @@ int fuse_bpf_copy_file_range(ssize_t *out, struct inode *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); @@ -925,6 +1119,22 @@ static int fuse_fsync_initialize_out(struct bpf_fuse_args *fa, struct fuse_fsync return 0; } +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_meta_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) { @@ -947,6 +1157,7 @@ int fuse_bpf_fsync(int *out, struct inode *inode, struct 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); } @@ -981,10 +1192,27 @@ static int fuse_dir_fsync_initialize_out(struct bpf_fuse_args *fa, struct fuse_f return 0; } +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, loff_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); } @@ -1054,6 +1282,22 @@ static int fuse_getxattr_initialize_out(struct bpf_fuse_args *fa, return 0; } +static int fuse_getxattr_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_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->value, &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) @@ -1099,6 +1343,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); } @@ -1151,6 +1396,22 @@ static int fuse_listxattr_initialize_out(struct bpf_fuse_args *fa, return 0; } +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->out); + 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) { @@ -1190,6 +1451,7 @@ int fuse_bpf_listxattr(ssize_t *out, struct inode *inode, 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); } @@ -1255,6 +1517,22 @@ static int fuse_setxattr_initialize_out(struct bpf_fuse_args *fa, return 0; } +static int fuse_setxattr_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, + struct fuse_setxattr_args *args) +{ + if (ops->setxattr_prefilter) + return ops->setxattr_prefilter(meta, &args->in, &args->name, &args->value); + 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->value); + return BPF_FUSE_CONTINUE; +} + static int fuse_setxattr_backing(struct bpf_fuse_args *fa, int *out, struct dentry *dentry, const char *name, const void *value, size_t size, int flags) @@ -1278,6 +1556,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); } @@ -1314,6 +1593,22 @@ static int fuse_removexattr_initialize_out(struct bpf_fuse_args *fa, return 0; } +static int fuse_removexattr_prefilter(struct fuse_ops *ops, struct bpf_fuse_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_fuse_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) { @@ -1334,6 +1629,7 @@ int fuse_bpf_removexattr(int *out, struct inode *inode, 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); } @@ -1424,6 +1720,21 @@ static int fuse_file_read_iter_initialize_out(struct bpf_fuse_args *fa, struct f return 0; } +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; +} // TODO: use backing-file.c static inline rwf_t fuse_iocb_to_rw_flags(int ifl, int iocb_mask) @@ -1498,6 +1809,8 @@ int fuse_bpf_file_read_iter(ssize_t *out, struct inode *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); @@ -1547,6 +1860,22 @@ static int fuse_file_write_iter_initialize_out(struct bpf_fuse_args *fa, return 0; } +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_fuse_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) { @@ -1611,6 +1940,8 @@ int fuse_bpf_file_write_iter(ssize_t *out, struct inode *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); @@ -1717,6 +2048,22 @@ static int fuse_file_fallocate_initialize_out(struct bpf_fuse_args *fa, return 0; } +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) { @@ -1739,6 +2086,8 @@ int fuse_bpf_file_fallocate(int *out, struct inode *inode, 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); @@ -1806,6 +2155,22 @@ static int fuse_lookup_initialize_out(struct bpf_fuse_args *fa, struct fuse_look return 0; } +static int fuse_lookup_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_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_meta_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 **out, struct inode *dir, struct dentry *entry, unsigned int flags) { @@ -1987,6 +2352,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); } @@ -2053,6 +2419,22 @@ static int fuse_mknod_initialize_out(struct bpf_fuse_args *fa, struct fuse_mknod return 0; } +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_meta_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) { @@ -2105,6 +2487,7 @@ int fuse_bpf_mknod(int *out, struct inode *dir, struct 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); } @@ -2206,10 +2589,27 @@ static int fuse_mkdir_finalize(struct bpf_fuse_args *fa, int *out, return 0; } +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_meta_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, umode_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); } @@ -2243,6 +2643,22 @@ static int fuse_rmdir_initialize_out(struct bpf_fuse_args *fa, struct fuse_buffe return 0; } +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_meta_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) { @@ -2277,6 +2693,7 @@ 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_prefilter, fuse_rmdir_postfilter, fuse_rmdir_backing, fuse_rmdir_finalize, dir, entry); } @@ -2419,6 +2836,22 @@ static int fuse_rename2_initialize_out(struct bpf_fuse_args *fa, struct fuse_ren return 0; } +static int fuse_rename2_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, + struct fuse_rename2_args *args) +{ + if (ops->rename2_prefilter) + return ops->rename2_prefilter(meta, &args->in, &args->old_name, &args->new_name); + return BPF_FUSE_CONTINUE; +} + +static int fuse_rename2_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_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, @@ -2446,6 +2879,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); } @@ -2506,6 +2940,22 @@ static int fuse_rename_initialize_out(struct bpf_fuse_args *fa, struct fuse_rena return 0; } +static int fuse_rename_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, + struct fuse_rename_args *args) +{ + if (ops->rename_prefilter) + return ops->rename_prefilter(meta, &args->in, &args->old_name, &args->new_name); + return BPF_FUSE_CONTINUE; +} + +static int fuse_rename_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, + struct fuse_rename_args *args) +{ + if (ops->rename_postfilter) + return ops->rename_postfilter(meta, &args->in, &args->old_name, &args->new_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) @@ -2527,6 +2977,7 @@ int fuse_bpf_rename(int *out, struct inode *olddir, struct 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); } @@ -2560,6 +3011,22 @@ static int fuse_unlink_initialize_out(struct bpf_fuse_args *fa, struct fuse_buff return 0; } +static int fuse_unlink_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_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_meta_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; @@ -2596,6 +3063,7 @@ 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_prefilter, fuse_unlink_postfilter, fuse_unlink_backing, fuse_unlink_finalize, dir, entry); } @@ -2648,6 +3116,23 @@ static int fuse_link_initialize_out(struct bpf_fuse_args *fa, struct fuse_link_a return 0; } +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 dentry *entry, struct inode *dir, struct dentry *newent) { @@ -2715,6 +3200,7 @@ int fuse_bpf_link(int *out, struct inode *inode, struct 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); } @@ -2763,6 +3249,22 @@ static int fuse_getattr_initialize_out(struct bpf_fuse_args *fa, struct fuse_get return 0; } +static int fuse_getattr_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_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_meta_info *meta, + struct fuse_getattr_args *args) +{ + if (ops->getattr_postfilter) + return ops->getattr_postfilter(meta, &args->in, &args->out); + return BPF_FUSE_CONTINUE; +} + /* TODO: unify with overlayfs */ static inline int fuse_do_getattr(const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) @@ -2831,6 +3333,7 @@ int fuse_bpf_getattr(int *out, struct inode *inode, const 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); } @@ -2910,6 +3413,22 @@ static int fuse_setattr_initialize_out(struct bpf_fuse_args *fa, struct fuse_set return 0; } +static int fuse_setattr_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_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_meta_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) { @@ -2947,6 +3466,7 @@ int fuse_bpf_setattr(int *out, struct inode *inode, struct 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); } @@ -2976,6 +3496,22 @@ static int fuse_statfs_initialize_out(struct bpf_fuse_args *fa, struct fuse_stat return 0; } +static int fuse_statfs_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_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_meta_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) { @@ -3011,6 +3547,7 @@ int fuse_bpf_statfs(int *out, struct inode *inode, struct 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); } @@ -3080,6 +3617,22 @@ static int fuse_get_link_initialize_out(struct bpf_fuse_args *fa, struct fuse_ge return 0; } +static int fuse_get_link_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_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 **out, struct inode *inode, struct dentry *dentry, struct delayed_call *callback) @@ -3119,6 +3672,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); } @@ -3169,6 +3723,22 @@ static int fuse_symlink_initialize_out(struct bpf_fuse_args *fa, struct fuse_sym return 0; } +static int fuse_symlink_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_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_meta_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) { @@ -3219,6 +3789,7 @@ int fuse_bpf_symlink(int *out, struct inode *dir, struct 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); } @@ -3292,6 +3863,22 @@ static int fuse_readdir_initialize_out(struct bpf_fuse_args *fa, struct fuse_rea return 0; } +static int fuse_readdir_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_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_meta_info *meta, + struct fuse_read_args *args) +{ + if (ops->readdir_postfilter) + return ops->readdir_postfilter(meta, &args->in, &args->out, &args->buffer); + return BPF_FUSE_CONTINUE; +} + struct fusebpf_ctx { struct dir_context ctx; u8 *addr; @@ -3415,6 +4002,7 @@ int fuse_bpf_readdir(int *out, struct inode *inode, struct file *file, struct di again: ret = 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 >= 0) { @@ -3451,6 +4039,22 @@ static int fuse_access_initialize_out(struct bpf_fuse_args *fa, struct fuse_acce return 0; } +static int fuse_access_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_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_meta_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 = get_fuse_inode(inode); @@ -3469,6 +4073,7 @@ 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_prefilter, fuse_access_postfilter, fuse_access_backing, fuse_access_finalize, inode, mask); } From patchwork Fri Mar 29 01:53:46 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609908 X-Patchwork-Delegate: bpf@iogearbox.net Received: from mail-yw1-f201.google.com (mail-yw1-f201.google.com [209.85.128.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 61C7B137776 for ; Fri, 29 Mar 2024 01:55:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677313; cv=none; b=b/Ps0Y4Z8xTHnBH0TgvEMTa/nX4Y1z2TE+CIpFpfvTTj0TkQtp1flbK43HNsFlnFhXE5krYP3Wj1WefPgVgSN4+vY9e5pQ7CcZUM5uI6deVsCMDd0kJywnCNt8gMTZC52Ho5Sdd7TbS11aJe+FX5W0CSpDPFC+jwgJ7UkqOI+fs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677313; c=relaxed/simple; bh=0MMp9URgTl3q02pasvnZIboYGP3n5FtVhyJWa7EOO6U=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=h7e5O3TfztTbeh2cTuVlKbbIAsoekAeHDORS/GX8iIEeBf87Kbt5F1e4QKNXfWmGYFAtv5wRckTBR7IlIi5zX6RdmphC71iSDZIYrXYJWgSLmh/5rDaSoEPTzSElGREbKAszQGGtsBB9J/wpOJdv8RKqUeumpx1oTrMmy6wCsuQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=3rr1EVrE; arc=none smtp.client-ip=209.85.128.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="3rr1EVrE" Received: by mail-yw1-f201.google.com with SMTP id 00721157ae682-611e89cf3b5so25771857b3.3 for ; Thu, 28 Mar 2024 18:55:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677309; x=1712282109; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=fxI1XKtAfOqWqF4S724gatu41by9k0x994bvTDA+8mc=; b=3rr1EVrE6vJz5zZF0MzFKSOApyxCJ1gWuHiMuZDYKpI8SbQUtCqxeXFDcEOGIFH88r mClnbvXi/+L/gSNJu+c2u95mrWUtd1+HkiePHZO2uK80ajWF0sONYoSvCENaaHtWux9u 7LHVEKz51rcXRgI0it0YeAjaWdleI3IKG6z/V73ZPx6Z4LQmv8JgBXo6TXZvo98DroLO a+C0vXSFAfXXT+xjVHZyZV5j/KtDnNC0+eUCU2WcdfiGWko+4BYaaW5DRdKyYDCdO10l tf/7vntb5t8v/zLZDHD2cJwlAurhKByKvrYEV9lGTk9fFx6P1JmWAfqz+Sb3+S3Wvaef 0LgA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677309; x=1712282109; 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=fxI1XKtAfOqWqF4S724gatu41by9k0x994bvTDA+8mc=; b=FOUeICEhxRpUxh318wT41t3duVO/JLtiRB6h8JFFq7a/ye4vVBDMtUT2+893k1GzOM tmCPa6FS5rCOexkJaRTs078hi/OzZ7AVC+g1a4BTr4byxYb6r7txIieFK3RnUBSvTwRh 48XLWQtOuA71VLUMHMkMH0MxT+zxcFvfQFilae/+bccNdO3C38HcWlFu548g5YRf41Yr qFDue1yT1BQWSkrVFIkP8hDrT78wTL4Dn6Vg68egC9a2KWCZ29AQJ6kuYUc69qsrvPjS 6/8SC0hBvJyb9vCLWBj9BAZfKjS3PzTYg353ZP27jCipdbn7VLjuKUMIaBmeCadqV4n2 uusw== X-Forwarded-Encrypted: i=1; AJvYcCVUCdMvr4AmYtPrkCoRd/ZlwqhiAq8AFBDhiu27H8OnvmD4k1jCjMnfaR73MybzR7Nqi/o2VfoRx2c/nhf2Wl8PY5gv X-Gm-Message-State: AOJu0YwDyzVLWiiveQqqgmEfLZ/XVaWvF8HN966rnizoe7wRhUQBYlmW jTSHjKQ31TfuVzVvs+ebbrngY0+tQNXlZowGBS1KeAC3uMPg4SKGMh+hJDM26pQZMsjrKJlCJpt 9Aw== X-Google-Smtp-Source: AGHT+IGNBTlq0+EGPMU+HiHxZbFEWcdafvcP8XEsPnWaMVxWsw+zlkZHIH5AxV5uO27CGTp5IKDk+ZlrdgI= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a0d:ca83:0:b0:614:26ed:19a9 with SMTP id m125-20020a0dca83000000b0061426ed19a9mr273364ywd.3.1711677309339; Thu, 28 Mar 2024 18:55:09 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:46 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-32-drosen@google.com> Subject: [RFC PATCH v4 31/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-Delegate: bpf@iogearbox.net X-Patchwork-State: RFC 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 8172144e557d..9227e62b8734 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -16,6 +16,163 @@ #include #include +static void set_in_args(struct fuse_in_arg *dst, struct bpf_fuse_arg *src) +{ + if (src->is_buffer) { + struct fuse_buffer *buffer = src->buffer; + + *dst = (struct fuse_in_arg) { + .size = buffer->size, + .value = buffer->data, + }; + } else { + *dst = (struct fuse_in_arg) { + .size = src->size, + .value = src->value, + }; + } +} + +static void set_out_args(struct fuse_arg *dst, struct bpf_fuse_arg *src) +{ + if (src->is_buffer) { + struct fuse_buffer *buffer = src->buffer; + + // Userspace out args presents as much space as needed + *dst = (struct fuse_arg) { + .size = buffer->max_size, + .value = buffer->data, + }; + } else { + *dst = (struct fuse_arg) { + .size = src->size, + .value = 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 = fuse_ext_size(sizeof(*err_in)); + + xh = extend_arg(ext, err_in_size); + if (!xh) + return -ENOMEM; + xh->size = err_in_size; + xh->type = FUSE_ERROR_IN; + + err_in = (uint32_t *)&xh[1]; + *err_in = error; + return 0; +} + +static int get_filter_ext(struct fuse_args *args) +{ + struct fuse_in_arg ext = { .size = 0, .value = NULL }; + int err = 0; + + if (args->is_filter) + err = get_err_in(args->error_in, &ext); + if (!err && ext.size) { + WARN_ON(args->in_numargs >= ARRAY_SIZE(args->in_args)); + args->is_ext = true; + args->ext_idx = args->in_numargs++; + args->in_args[args->ext_idx] = ext; + } else { + kfree(ext.value); + } + return err; +} + +static ssize_t fuse_bpf_simple_request(struct fuse_mount *fm, struct bpf_fuse_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 = { + .nodeid = fa->info.nodeid, + .opcode = fa->info.opcode, + .error_in = fa->info.error_in, + .in_numargs = in_numargs, + .out_numargs = out_numargs, + .force = !!(fa->flags & FUSE_BPF_FORCE), + .out_argvar = !!(fa->flags & FUSE_BPF_OUT_ARGVAR), + .is_lookup = !!(fa->flags & FUSE_BPF_IS_LOOKUP), + .is_filter = true, + }; + + /* All out args must be writeable */ + for (i = 0; i < out_numargs; ++i) { + struct fuse_buffer *buffer; + + if (!out_arg_array[i].is_buffer) + continue; + buffer = out_arg_array[i].buffer; + if (!bpf_fuse_get_writeable(buffer, buffer->max_size, true)) + return -ENOMEM; + } + + /* Set in args */ + for (i = 0; i < fa->in_numargs; ++i) + set_in_args(&args.in_args[i], &fa->in_args[i]); + if (add_out_to_in) { + for (i = 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 = 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 = out_arg_array[out_numargs - 1].buffer; + + if (buff->flags & BPF_FUSE_VARIABLE_SIZE) + args.out_argvar = true; + } + if (add_out_to_in) { + res = get_filter_ext(&args); + if (res) + return res; + } + res = fuse_simple_request(fm, &args); + + /* update used areas of buffers */ + for (i = 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 = args.out_args[i].size; + fa->ret = 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 = 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, struct 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; @@ -62,9 +219,11 @@ static inline void bpf_fuse_free_alloced(struct bpf_fuse_args *fa) ({ \ struct fuse_inode *fuse_inode = get_fuse_inode(inode); \ struct fuse_ops *fuse_ops = fuse_inode->bpf_ops; \ + struct fuse_mount *fm = get_fuse_mount(inode); \ struct bpf_fuse_args fa = { 0 }; \ bool initialized = false; \ bool handled = false; \ + bool locked; \ ssize_t res; \ int bpf_next; \ io feo = { 0 }; \ @@ -90,6 +249,16 @@ static inline void bpf_fuse_free_alloced(struct bpf_fuse_args *fa) break; \ } \ \ + if (bpf_next == BPF_FUSE_USER_PREFILTER) { \ + locked = fuse_lock_inode(inode); \ + res = fuse_prefilter_simple_request(fm, &fa); \ + fuse_unlock_inode(inode, locked); \ + if (res < 0) { \ + error = res; \ + break; \ + } \ + bpf_next = fa.ret; \ + } \ bpf_fuse_set_in_immutable(&fa); \ \ error = initialize_out(&fa, &feo, args); \ @@ -119,6 +288,16 @@ static inline void bpf_fuse_free_alloced(struct bpf_fuse_args *fa) break; \ } \ \ + if (!(bpf_next == BPF_FUSE_USER_POSTFILTER)) \ + break; \ + \ + locked = fuse_lock_inode(inode); \ + res = fuse_postfilter_simple_request(fm, &fa); \ + fuse_unlock_inode(inode, locked); \ + if (res < 0) { \ + error = res; \ + break; \ + } \ } while (false); \ \ if (initialized && handled) { \ diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index e51ec198af3f..fca2ca440f55 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -521,6 +521,8 @@ ssize_t fuse_simple_request(struct fuse_mount *fm, struct fuse_args *args) BUG_ON(args->out_numargs == 0); ret = args->out_args[args->out_numargs - 1].size; } + if (args->is_filter && args->is_ext) + args->ret = req->out.h.error; fuse_put_request(req); return ret; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index f5ab3b79b3cc..2167cbd04b0c 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -640,7 +640,7 @@ static int get_security_context(struct dentry *entry, umode_t mode, return err; } -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 = buf->size + bytes; @@ -660,7 +660,7 @@ static void *extend_arg(struct fuse_in_arg *buf, u32 bytes) return p + newlen - bytes; } -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); } @@ -720,7 +720,7 @@ static int get_create_ext(struct fuse_args *args, return err; } -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 665673d91753..c75822c433a0 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -324,6 +324,17 @@ struct fuse_page_desc { unsigned int offset; }; +/* To deal with bpf pre and post filters in userspace calls, we must support + * 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; @@ -342,10 +353,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); }; @@ -1194,6 +1207,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); +/** + * 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 3417717c1a55..b37d5b015454 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -566,6 +566,7 @@ enum fuse_ext_type { /* Types 0..31 are reserved for fuse_secctx_header */ FUSE_MAX_NR_SECCTX = 31, FUSE_EXT_GROUPS = 32, + FUSE_ERROR_IN = 33, }; enum fuse_opcode { From patchwork Fri Mar 29 01:53:47 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609909 X-Patchwork-Delegate: bpf@iogearbox.net Received: from mail-yw1-f202.google.com (mail-yw1-f202.google.com [209.85.128.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 81DE513AD38 for ; Fri, 29 Mar 2024 01:55:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677314; cv=none; b=WA/TXrV2Zui3tlKhJzfAmcaRTYUu7oO1OY6OVtdJ1Ma5mdaFrllk3qWEcCR9Bbmn7ilWt7BCuFYHy6V0gwyghEJeiQQ2P2viLrgWCGSd9ZPO4XRIkgMY8suM899aX9lE+pIO2ddRSMmDd0yvOp0hrpxFBLZGZT+rp4r2FYGjrAU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677314; c=relaxed/simple; bh=G/uTaIAkskzGcC6fM2pfc7BhIzgm1a5+YIJNknGkYno=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=rhz3czNf/MD39hNCb+bTpJPhjmbUpdAfIGQN6YKtBtYN5D/ykvJoEkwg6oeOoPaI7oX13k+FA0C0Re/taMJ7Bxy6IgAWeOUNAUMzOO1LD5qN5m+KjuG3GN3KoggqDxCAO+82gVEZNJGCrrFTdeldVVF9OkCbnzp2X18Pz6GFWtw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=q9s4HHGP; arc=none smtp.client-ip=209.85.128.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="q9s4HHGP" Received: by mail-yw1-f202.google.com with SMTP id 00721157ae682-609f2920b53so23606907b3.0 for ; Thu, 28 Mar 2024 18:55:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677311; x=1712282111; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=7qesMvHPna2VGRQ3U+YOYTfZovmbGlRRDeWOIBEA3hQ=; b=q9s4HHGP1Y4POoajIPrDkRFEPdCRvS2tvrX+6OGtYUOgo4NIqyDXnOGi/KdTybGEet PQuVzJx312TkRUDLhPHbLLp2eesNLcj5SibFtAQpaPyFOMxS6FJa2ls9VhhB3WKCKdeE Wl2nvCV2mum0lFcjNmQ0oZ6ghC6BWszbC6BJCug9HPk62J7fLYPrd01vRMG9JuK1/Eyi eVt9xgde4BAUtQthbHOAF3WOTHlnmG2IbmAQJU8ev3xpRtu7S4IfkK6nDehk8TWKlbsc QPdg8fyRXRqCvJRUnrgNpMFM+q5eD5CCHdrP/nwMAOL+fdIgGKqYxkOFdrWs7B8j5QHK crRg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677311; x=1712282111; 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=7qesMvHPna2VGRQ3U+YOYTfZovmbGlRRDeWOIBEA3hQ=; b=YKyn76s9FS9PK+oJ5o4UAU+qaKykHk18bBp0Wgr1BQnqmtn1qRYuqeJuG1T0mkTX14 pPU3l7duP0zvjeIsMXXHsz/GFs6+gHU1smgXJMu/KY90J+wq753N3aK6eeX4zmlIGHDU XVeRcQjNyOKun8X6zZFGBqCgEtnyMbciRwDW9Y97ByGGx2mqe1T7uUY+sHItdFy/VtTG BqXe2ms5lFUxUywIaHX5dSBJC3/7GOxnte0klNtVkLnnk2cj9k/SFO9sJ2WEYCXL9tJb 8HXfg+f1HI5dGIFKxmK9VzgiGB4tnv9ol6GP06AkH3hq0JjxWnR7xotWe3uepsO3v4vU 5XGw== X-Forwarded-Encrypted: i=1; AJvYcCUh/m/rw5JG6Qdyv1lcA/Vxpxmn2sfxun78R5V40ahYcehgYCXv4D21SkuZUvFMIwRhbN9C3zwv7ptQo3qbuLlA6OWF X-Gm-Message-State: AOJu0Ywrh643TJLX8kTqjdoP51iy1inA2XAvzLmRLFooOhwRg6Rxcq9v l/F+NbD5DfBHVios6G8dQU1upnA4ze1+fhd/ExVhW0RRVP3S7UmxoBYVFDRS7jTLVNvJjIC+R1u AZA== X-Google-Smtp-Source: AGHT+IGF9nW/lcLEYaeyMEZVwCHFKdI51EuE9NyugoSApHlrjqOTG5u7cGkQtddkr8oJLGW/eZgzFmc/WjM= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:2701:b0:dc2:5273:53f9 with SMTP id dz1-20020a056902270100b00dc2527353f9mr86822ybb.1.1711677311477; Thu, 28 Mar 2024 18:55:11 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:47 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-33-drosen@google.com> Subject: [RFC PATCH v4 32/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-Delegate: bpf@iogearbox.net X-Patchwork-State: RFC 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 reported error code }; struct bpf_fuse_args { From patchwork Fri Mar 29 01:53:48 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609910 X-Patchwork-Delegate: bpf@iogearbox.net Received: from mail-yw1-f202.google.com (mail-yw1-f202.google.com [209.85.128.202]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 40B0F13B5A0 for ; Fri, 29 Mar 2024 01:55:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.202 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677316; cv=none; b=rB4DPXKJ7saaBDA3jCuzTJ2xa0BvCFC7zjj6SY9d0+RpcZ6+8+YQNuuIGqBcYV1RsiFSxarEg9dKEyPGjdRQJVyMdYXInjYIzdCfhGJ+ZjnrpChkJppIoKC0j7i+q7rlRzUFpCewA/EmZh14U//f/6V5Kb80HkCPSjwg79/HuVw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677316; c=relaxed/simple; bh=sqy3xAPgmjgOiWHaXJZZi1ooyhcDWUkMY7GvNQ4f4EQ=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=HfFeejWTM2hB1NRSD7YkduU3hd0nNMHJnlO8Cw4hRpur+FlMXIGVAsQOAWKwPzl1VgFvUt338nYzFVmG2dtLZq6YN8HWNbg0CxLAv0rOnEJUOpCZCy4GJBC8ds+lgHVx9g4Az+HDC7yM2Tsfrr6zGRGT2CSU97nJ4IK9o68pkD4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=EhIZlclk; arc=none smtp.client-ip=209.85.128.202 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="EhIZlclk" Received: by mail-yw1-f202.google.com with SMTP id 00721157ae682-60a08eb0956so25469487b3.3 for ; Thu, 28 Mar 2024 18:55:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677313; x=1712282113; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=dc0/1tSRu57hk2+lOEsBiEE+YFsUFGOWDNxYukZQ2Us=; b=EhIZlclkOK4/1KI15NEBKCSrzt0al0Nnw5GpN9W8hi/5riwvqUchqEa3ci7OUcSTtz 6hAimfpK/aleBDLJcmkirbm0XDaiwqmWkJ/L1R/CILT/El41vZP5rspLH0UGp6Bw2Vee ewBXUYGFvoDEDJLthGvXd4ORV/Dx1tIrmp0CyVtkBn3dPhEPxOcmpso/SKS6SJLVh946 ygJXRXyF/kqJXrvfVayGZ0WDIwZV6niMgA8HNkcD6wuBPSyjwtslzmq/eSmwgXYAeNeY ufJiwELyKhoFZtRkguNdCcVm5UXZJbdI9qBwvJT73rS2vbHcIYaGpDkSveirt916NyvX 27+w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677313; x=1712282113; 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=dc0/1tSRu57hk2+lOEsBiEE+YFsUFGOWDNxYukZQ2Us=; b=gQSr6UuM6rK0R6qHoHw3FzudhDkpQe3WrHEUEBnUM6BqQkMqFELyjFG2tzDnErrlF+ YJGGoWOQM83R6Xxr+xfrNqR3wnRmwXLDoZka/LfFBnrkg2R+fNPmpx59INt4f/v/7Y2H Hmwofetv69GB37HhIjQe6ZdTDXiEtYDL+6wO/jTdsLckJHcLajxyTwks0Rs7pzeC3o48 t2mNUAQXUqvsVOSnLaCS//3qH15tfQInlEvw/ulicdYEQhasYWLvJNDnN+KaNNjJXdcE lMKils4eRvEqCxosygeBg0clqARipaYoIIyEaHbym3kGeMRBJgHMhVuOo9FS1OBfAX7Y mm4g== X-Forwarded-Encrypted: i=1; AJvYcCUgj/Vcm5XQUokoukxGYJhef+e9WSXYBIEj/4uUWH89r8Ppxb/0dw/xlFp29VrX+e6I2u8ECu4kCzuRyIIN23lK9KKf X-Gm-Message-State: AOJu0Yx27UZc9cViboVyqFUzxYoXQ0a89G8uz1Wbmdc4N/SdzXC4ygdL g7Itzc5VFt5GqlNvndStTN0Z17JNuVLSkjfiY/iVuQzuTxOLZsvB3x13RzJSeWrXAlD7P2cIouw iVQ== X-Google-Smtp-Source: AGHT+IFKaOhfB8pTm+iz3QIKSOTOmM9kTS5fpl2hYAvm7gIXStf1F4lwEArEBONetKY1pkXKJrMPSbPJmmo= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:220e:b0:dc7:865b:22c6 with SMTP id dm14-20020a056902220e00b00dc7865b22c6mr86787ybb.8.1711677313466; Thu, 28 Mar 2024 18:55:13 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:48 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-34-drosen@google.com> Subject: [RFC PATCH v4 33/36] fuse-bpf: Add default filter op 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-Delegate: bpf@iogearbox.net X-Patchwork-State: RFC Add a default bpf func to call if the specific one is not present. Useful if you wish to track when different ops are handled. Signed-off-by: Daniel Rosenberg --- fs/fuse/backing.c | 136 +++++++++++++++++++++------------------ include/linux/bpf_fuse.h | 2 + include/uapi/linux/bpf.h | 1 + 3 files changed, 77 insertions(+), 62 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 9227e62b8734..ec554a2bc93f 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -244,6 +244,12 @@ static inline void bpf_fuse_free_alloced(struct bpf_fuse_args *fa) &fa.info, &feo); \ else \ bpf_next = BPF_FUSE_CONTINUE; \ + if (bpf_next == BPF_FUSE_CALL_DEFAULT) { \ + if (fuse_ops->default_filter) \ + bpf_next = fuse_ops->default_filter(&fa.info); \ + else \ + bpf_next = BPF_FUSE_CONTINUE; \ + } \ if (bpf_next < 0) { \ error = bpf_next; \ break; \ @@ -283,6 +289,12 @@ static inline void bpf_fuse_free_alloced(struct bpf_fuse_args *fa) fa.info.opcode |= FUSE_POSTFILTER; \ if (bpf_next == BPF_FUSE_POSTFILTER) \ bpf_next = call_postfilter(fuse_ops, &fa.info, &feo);\ + if (bpf_next == BPF_FUSE_CALL_DEFAULT) { \ + if (fuse_ops->default_filter) \ + bpf_next = fuse_ops->default_filter(&fa.info); \ + else \ + bpf_next = BPF_FUSE_CONTINUE; \ + } \ if (bpf_next < 0) { \ error = bpf_next; \ break; \ @@ -611,7 +623,7 @@ static int fuse_open_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info * if (ops->opendir_prefilter) return ops->opendir_prefilter(meta, &open->in); } - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_open_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -625,7 +637,7 @@ static int fuse_open_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info if (ops->opendir_postfilter) return ops->opendir_postfilter(meta, &open->in, &open->out); } - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_open_backing(struct bpf_fuse_args *fa, int *out, @@ -767,7 +779,7 @@ static int fuse_create_open_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta { if (ops->create_open_prefilter) return ops->create_open_prefilter(meta, &args->in, &args->name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_create_open_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -775,7 +787,7 @@ static int fuse_create_open_postfilter(struct fuse_ops *ops, struct bpf_fuse_met { 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; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_open_file_backing(struct inode *inode, struct file *file) @@ -931,7 +943,7 @@ static int fuse_release_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_inf { if (ops->release_prefilter) return ops->release_prefilter(meta, args); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_release_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -939,7 +951,7 @@ static int fuse_release_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_in { if (ops->release_postfilter) return ops->release_postfilter(meta, args); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_releasedir_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -947,7 +959,7 @@ static int fuse_releasedir_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_ { if (ops->releasedir_prefilter) return ops->releasedir_prefilter(meta, args); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_releasedir_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -955,7 +967,7 @@ static int fuse_releasedir_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta { if (ops->releasedir_postfilter) return ops->releasedir_postfilter(meta, args); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_release_backing(struct bpf_fuse_args *fa, int *out, @@ -1022,7 +1034,7 @@ static int fuse_flush_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->flush_prefilter) return ops->flush_prefilter(meta, args); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_flush_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -1030,7 +1042,7 @@ static int fuse_flush_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->flush_postfilter) return ops->flush_postfilter(meta, args); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_flush_backing(struct bpf_fuse_args *fa, int *out, struct file *file, fl_owner_t id) @@ -1102,7 +1114,7 @@ static int fuse_lseek_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->lseek_prefilter) return ops->lseek_prefilter(meta, &args->in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_lseek_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -1110,7 +1122,7 @@ static int fuse_lseek_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->lseek_postfilter) return ops->lseek_postfilter(meta, &args->in, &args->out); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_lseek_backing(struct bpf_fuse_args *fa, loff_t *out, @@ -1217,7 +1229,7 @@ static int fuse_copy_file_range_prefilter(struct fuse_ops *ops, struct bpf_fuse_ { if (ops->copy_file_range_prefilter) return ops->copy_file_range_prefilter(meta, &args->in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_copy_file_range_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -1225,7 +1237,7 @@ static int fuse_copy_file_range_postfilter(struct fuse_ops *ops, struct bpf_fuse { if (ops->copy_file_range_postfilter) return ops->copy_file_range_postfilter(meta, &args->in, &args->out); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_copy_file_range_backing(struct bpf_fuse_args *fa, ssize_t *out, struct file *file_in, @@ -1303,7 +1315,7 @@ static int fuse_fsync_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->fsync_prefilter) return ops->fsync_prefilter(meta, in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_fsync_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -1311,7 +1323,7 @@ static int fuse_fsync_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->fsync_postfilter) return ops->fsync_postfilter(meta, in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_fsync_backing(struct bpf_fuse_args *fa, int *out, @@ -1376,7 +1388,7 @@ static int fuse_dir_fsync_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_i { if (ops->dir_fsync_prefilter) return ops->fsync_prefilter(meta, in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_dir_fsync_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -1384,7 +1396,7 @@ static int fuse_dir_fsync_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_ { if (ops->dir_fsync_postfilter) return ops->dir_fsync_postfilter(meta, in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } int fuse_bpf_dir_fsync(int *out, struct inode *inode, struct file *file, loff_t start, loff_t end, int datasync) @@ -1466,7 +1478,7 @@ static int fuse_getxattr_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_in { if (ops->getxattr_prefilter) return ops->getxattr_prefilter(meta, &args->in, &args->name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_getxattr_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -1474,7 +1486,7 @@ static int fuse_getxattr_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_i { if (ops->getxattr_postfilter) return ops->getxattr_postfilter(meta, &args->in, &args->name, &args->value, &args->out); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_getxattr_backing(struct bpf_fuse_args *fa, int *out, @@ -1580,7 +1592,7 @@ static int fuse_listxattr_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_i { if (ops->listxattr_prefilter) return ops->listxattr_prefilter(meta, &args->in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_listxattr_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -1588,7 +1600,7 @@ static int fuse_listxattr_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_ { if (ops->listxattr_postfilter) return ops->listxattr_postfilter(meta, &args->in, &args->value, &args->out); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_listxattr_backing(struct bpf_fuse_args *fa, ssize_t *out, struct dentry *dentry, @@ -1701,7 +1713,7 @@ static int fuse_setxattr_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_in { if (ops->setxattr_prefilter) return ops->setxattr_prefilter(meta, &args->in, &args->name, &args->value); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_setxattr_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -1709,7 +1721,7 @@ static int fuse_setxattr_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_i { if (ops->setxattr_postfilter) return ops->setxattr_postfilter(meta, &args->in, &args->name, &args->value); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_setxattr_backing(struct bpf_fuse_args *fa, int *out, struct dentry *dentry, @@ -1777,7 +1789,7 @@ static int fuse_removexattr_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta { if (ops->removexattr_prefilter) return ops->removexattr_prefilter(meta, in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_removexattr_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -1785,7 +1797,7 @@ static int fuse_removexattr_postfilter(struct fuse_ops *ops, struct bpf_fuse_met { if (ops->removexattr_postfilter) return ops->removexattr_postfilter(meta, in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_removexattr_backing(struct bpf_fuse_args *fa, int *out, @@ -1904,7 +1916,7 @@ static int fuse_file_read_iter_prefilter(struct fuse_ops *ops, struct bpf_fuse_m { if (ops->read_iter_prefilter) return ops->read_iter_prefilter(meta, &args->in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_file_read_iter_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -1912,7 +1924,7 @@ static int fuse_file_read_iter_postfilter(struct fuse_ops *ops, struct bpf_fuse_ { if (ops->read_iter_postfilter) return ops->read_iter_postfilter(meta, &args->in, &args->out); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } // TODO: use backing-file.c @@ -2044,7 +2056,7 @@ static int fuse_write_iter_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_ { if (ops->write_iter_prefilter) return ops->write_iter_prefilter(meta, &args->in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_write_iter_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -2052,7 +2064,7 @@ static int fuse_write_iter_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta { if (ops->write_iter_postfilter) return ops->write_iter_postfilter(meta, &args->in, &args->out); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_file_write_iter_backing(struct bpf_fuse_args *fa, ssize_t *out, @@ -2232,7 +2244,7 @@ static int fuse_file_fallocate_prefilter(struct fuse_ops *ops, struct bpf_fuse_m { if (ops->file_fallocate_prefilter) return ops->file_fallocate_prefilter(meta, in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_file_fallocate_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -2240,7 +2252,7 @@ static int fuse_file_fallocate_postfilter(struct fuse_ops *ops, struct bpf_fuse_ { if (ops->file_fallocate_postfilter) return ops->file_fallocate_postfilter(meta, in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_file_fallocate_backing(struct bpf_fuse_args *fa, int *out, @@ -2339,7 +2351,7 @@ static int fuse_lookup_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->lookup_prefilter) return ops->lookup_prefilter(meta, &args->name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_lookup_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -2347,7 +2359,7 @@ static int fuse_lookup_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_inf { if (ops->lookup_postfilter) return ops->lookup_postfilter(meta, &args->name, &args->out, &args->bpf_entries); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_lookup_backing(struct bpf_fuse_args *fa, struct dentry **out, struct inode *dir, @@ -2603,7 +2615,7 @@ static int fuse_mknod_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->mknod_prefilter) return ops->mknod_prefilter(meta, &args->in, &args->name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_mknod_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -2611,7 +2623,7 @@ static int fuse_mknod_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->mknod_postfilter) return ops->mknod_postfilter(meta, &args->in, &args->name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_mknod_backing(struct bpf_fuse_args *fa, int *out, @@ -2773,7 +2785,7 @@ static int fuse_mkdir_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->mkdir_prefilter) return ops->mkdir_prefilter(meta, &args->in, &args->name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_mkdir_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -2781,7 +2793,7 @@ static int fuse_mkdir_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->mkdir_prefilter) return ops->mkdir_postfilter(meta, &args->in, &args->name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } int fuse_bpf_mkdir(int *out, struct inode *dir, struct dentry *entry, umode_t mode) @@ -2827,7 +2839,7 @@ static int fuse_rmdir_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->rmdir_prefilter) return ops->rmdir_prefilter(meta, name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_rmdir_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -2835,7 +2847,7 @@ static int fuse_rmdir_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->rmdir_postfilter) return ops->rmdir_postfilter(meta, name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_rmdir_backing(struct bpf_fuse_args *fa, int *out, @@ -3020,7 +3032,7 @@ static int fuse_rename2_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_inf { if (ops->rename2_prefilter) return ops->rename2_prefilter(meta, &args->in, &args->old_name, &args->new_name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_rename2_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -3028,7 +3040,7 @@ static int fuse_rename2_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_in { if (ops->rename2_postfilter) return ops->rename2_postfilter(meta, &args->in, &args->old_name, &args->new_name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_rename2_backing(struct bpf_fuse_args *fa, int *out, @@ -3124,7 +3136,7 @@ static int fuse_rename_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->rename_prefilter) return ops->rename_prefilter(meta, &args->in, &args->old_name, &args->new_name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_rename_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -3132,7 +3144,7 @@ static int fuse_rename_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_inf { if (ops->rename_postfilter) return ops->rename_postfilter(meta, &args->in, &args->old_name, &args->new_name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_rename_backing(struct bpf_fuse_args *fa, int *out, @@ -3195,7 +3207,7 @@ static int fuse_unlink_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->unlink_prefilter) return ops->unlink_prefilter(meta, name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_unlink_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -3203,7 +3215,7 @@ static int fuse_unlink_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_inf { if (ops->unlink_postfilter) return ops->unlink_postfilter(meta, name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_unlink_backing(struct bpf_fuse_args *fa, int *out, struct inode *dir, struct dentry *entry) @@ -3300,7 +3312,7 @@ static int fuse_link_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info * { if (ops->link_prefilter) return ops->link_prefilter(meta, &args->in, &args->name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_link_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -3308,7 +3320,7 @@ static int fuse_link_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->link_postfilter) return ops->link_postfilter(meta, &args->in, &args->name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } @@ -3433,7 +3445,7 @@ static int fuse_getattr_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_inf { if (ops->getattr_prefilter) return ops->getattr_prefilter(meta, &args->in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_getattr_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -3441,7 +3453,7 @@ static int fuse_getattr_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_in { if (ops->getattr_postfilter) return ops->getattr_postfilter(meta, &args->in, &args->out); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } /* TODO: unify with overlayfs */ @@ -3597,7 +3609,7 @@ static int fuse_setattr_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_inf { if (ops->setattr_prefilter) return ops->setattr_prefilter(meta, &args->in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_setattr_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -3605,7 +3617,7 @@ static int fuse_setattr_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_in { if (ops->setattr_postfilter) return ops->setattr_postfilter(meta, &args->in, &args->out); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_setattr_backing(struct bpf_fuse_args *fa, int *out, @@ -3680,7 +3692,7 @@ static int fuse_statfs_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->statfs_prefilter) return ops->statfs_prefilter(meta); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_statfs_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -3688,7 +3700,7 @@ static int fuse_statfs_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_inf { if (ops->statfs_postfilter) return ops->statfs_postfilter(meta, out); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_statfs_backing(struct bpf_fuse_args *fa, int *out, @@ -3801,7 +3813,7 @@ static int fuse_get_link_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_in { if (ops->get_link_prefilter) return ops->get_link_prefilter(meta, &args->name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_get_link_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -3809,7 +3821,7 @@ static int fuse_get_link_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_i { if (ops->get_link_postfilter) return ops->get_link_postfilter(meta, &args->name); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_get_link_backing(struct bpf_fuse_args *fa, const char **out, @@ -3907,7 +3919,7 @@ static int fuse_symlink_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_inf { if (ops->symlink_prefilter) return ops->symlink_prefilter(meta, &args->name, &args->path); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_symlink_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -3915,7 +3927,7 @@ static int fuse_symlink_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_in { if (ops->symlink_postfilter) return ops->symlink_postfilter(meta, &args->name, &args->path); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_symlink_backing(struct bpf_fuse_args *fa, int *out, @@ -4047,7 +4059,7 @@ static int fuse_readdir_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_inf { if (ops->readdir_prefilter) return ops->readdir_prefilter(meta, &args->in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_readdir_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -4055,7 +4067,7 @@ static int fuse_readdir_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_in { if (ops->readdir_postfilter) return ops->readdir_postfilter(meta, &args->in, &args->out, &args->buffer); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } struct fusebpf_ctx { @@ -4223,7 +4235,7 @@ static int fuse_access_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info { if (ops->access_prefilter) return ops->access_prefilter(meta, in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_access_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *meta, @@ -4231,7 +4243,7 @@ static int fuse_access_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta_inf { if (ops->access_postfilter) return ops->access_postfilter(meta, in); - return BPF_FUSE_CONTINUE; + return BPF_FUSE_CALL_DEFAULT; } static int fuse_access_backing(struct bpf_fuse_args *fa, int *out, struct inode *inode, int mask) diff --git a/include/linux/bpf_fuse.h b/include/linux/bpf_fuse.h index 15646ba59c41..0747790c47ec 100644 --- a/include/linux/bpf_fuse.h +++ b/include/linux/bpf_fuse.h @@ -86,6 +86,8 @@ static inline unsigned bpf_fuse_arg_size(const struct bpf_fuse_arg *arg) } struct fuse_ops { + uint32_t (*default_filter)(const struct bpf_fuse_meta_info *meta); + 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, diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 46c1a3e3166d..fe8b485c9335 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -7467,6 +7467,7 @@ struct bpf_iter_num { #define BPF_FUSE_USER_PREFILTER 2 #define BPF_FUSE_POSTFILTER 3 #define BPF_FUSE_USER_POSTFILTER 4 +#define BPF_FUSE_CALL_DEFAULT 5 /* Op Code Filter values for BPF Programs */ #define FUSE_OPCODE_FILTER 0x0ffff From patchwork Fri Mar 29 01:53:49 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609911 Received: from mail-yb1-f201.google.com (mail-yb1-f201.google.com [209.85.219.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 745A313BADC for ; Fri, 29 Mar 2024 01:55:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677319; cv=none; b=sAFJcUNn4H3UU8/xrPl1MIKxOIb6hUzlA/ijTzA0jPdiaRrIkncFjVx/eBbcGUhoM3GO5jO1Y4hYWBWFf2uPd+ePnrFDInqxtGNwaCTY3gHrKecvPp7mu0KTbOdHLy+tujBw4u6SX9LObC3oDSLd+GOKdRaNG3l7SjE1W8UXBbs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677319; c=relaxed/simple; bh=0Uwazv+w/jcDgy7clXL/V1Mbi8BeuxwTAdno5aYWSXU=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=QqoqVb+yXfszvkpc2ooW6j9SORUKFajGJMGCqPpK/i+a/mQ/t/984sozoNvko0xcnIYZiGaRLPo+QfdSrsrJhxRZs0Y2DaAoMhVczDovtv6CG/WmkUptvryFRCYqAOFD8UH15nXyKy9UUvvIqwul6nT9+Hgzbn/5vIaBZc8cAso= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=4FzKBYhz; arc=none smtp.client-ip=209.85.219.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="4FzKBYhz" Received: by mail-yb1-f201.google.com with SMTP id 3f1490d57ef6-dc6dbdcfd39so2560630276.2 for ; Thu, 28 Mar 2024 18:55:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677315; x=1712282115; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=MtoMSiLbYsTIFk1WUdHLikHjCOt3vYMxnhJwxAPjDgo=; b=4FzKBYhzFOHauDnaW3IMSZUgY790mg4Gn7iRS7Whh++HHf5PydggAXeHn6MNtNiEdx cKg7L5CUjDo20yo8VVcfh1FOPO6DAzxs5VygUmkV8Nziz6UEissdAyYm8sfwNsndn265 9JO5SZ+oCYp5BbIOHHF+qLTkx2LXgoSRltF3jRHx3LGYQrrbujEZUwc6CI6kJqSazWbo HtTZPmPAzSrka8Rel0fiP/x8FEaKyJ0RI2hs4+xDcKDm+U2I16zHhZ6Lhkn4RAODxPqV CZIvlHoA1akiMTfV/K6bZCXFrXcFTEdbQT00sinGO5nbXXp/UAjld9gybn8kSfc6yAB4 b8bg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677315; x=1712282115; 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=MtoMSiLbYsTIFk1WUdHLikHjCOt3vYMxnhJwxAPjDgo=; b=ncA5Q+rSvI1EwOwdyPOqusgggRsJT+/A0dCG2tnbQ4y0l5B9PKuIYjof/6e5A1N10b tE1qahimEcYln92qRXUq7xg0HVOS/laOieNv8kMqBEintxVZxQwCkzhojPPfuPNWA1Wk iB9BZCLe7IYOz3AckJxJI7Ai4i74BuDG7AKbuu6AB99/apRxQnYtb9jVPjJuieGHE5/r /7s8JzmFJeFlBmvoRtddJFGf+FbFmjlQsjOvOxSLSc/sD+dJRSHARK+3V8M6qqBGI/wu ZxuypqvVvSmWy3aVl0Oi+kY4ceHbUvOCpbnTPAyurGtUe5t+xRXr63P1QD4K1vAzIRxL eYJA== X-Forwarded-Encrypted: i=1; AJvYcCV0fFeQOTnzOY0scr0155F3RZltIFQRBbaH1/8Fe9xAnmpyP9w3xlEA4HXFIQzZP9FeGWvjHjExN6Njs/iS0u47il0G X-Gm-Message-State: AOJu0Yw5AVyaF8UeNpCrirrb93EVB8JKPfMkBiLLB8ZwH70fe3qXKduR von0HOZjqJCOjnSOJhRjT+qJqwRx98edhpSkScPVJr/pNYjcEP6h9rOdj3f/8xeRD3z0WWQ16yF 3OQ== X-Google-Smtp-Source: AGHT+IGZeYre6FPbWGBQWr9BXmlxwEAHNqQ3mZNYTIqsIIXoLy4CLve2KamU2yv2psNwpCig9et+QacFbsM= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a05:6902:2313:b0:ddd:7581:1237 with SMTP id do19-20020a056902231300b00ddd75811237mr312324ybb.3.1711677315620; Thu, 28 Mar 2024 18:55:15 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:49 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-35-drosen@google.com> Subject: [RFC PATCH v4 34/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-State: RFC Updates the bpf includes under tools, and adds fuse Signed-off-by: Daniel Rosenberg --- tools/include/uapi/linux/bpf.h | 13 + tools/include/uapi/linux/fuse.h | 1197 +++++++++++++++++++++++++++++++ 2 files changed, 1210 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 85ec7fc799d7..fe8b485c9335 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -7461,4 +7461,17 @@ struct bpf_iter_num { __u64 __opaque[1]; } __attribute__((aligned(8))); +/* 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 +#define BPF_FUSE_CALL_DEFAULT 5 + +/* 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/fuse.h new file mode 100644 index 000000000000..b37d5b015454 --- /dev/null +++ b/tools/include/uapi/linux/fuse.h @@ -0,0 +1,1197 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */ +/* + 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 PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + 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, STRICT + 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_WRITE, + * 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 kernels + * + * 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_SUIDGID + * - 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 + * - add FUSE_HAS_EXPIRE_ONLY + * + * 7.39 + * - add FUSE_DIRECT_IO_ALLOW_MMAP + * - add FUSE_STATX and related structures + */ + +#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 39 + +/** 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; +}; + +/* + * The following structures are bit-for-bit compatible with the statx(2) ABI in + * Linux. + */ +struct fuse_sx_time { + int64_t tv_sec; + uint32_t tv_nsec; + int32_t __reserved; +}; + +struct fuse_statx { + uint32_t mask; + uint32_t blksize; + uint64_t attributes; + uint32_t nlink; + uint32_t uid; + uint32_t gid; + uint16_t mode; + uint16_t __spare0[1]; + uint64_t ino; + uint64_t size; + uint64_t blocks; + uint64_t attributes_mask; + struct fuse_sx_time atime; + struct fuse_sx_time btime; + struct fuse_sx_time ctime; + struct fuse_sx_time mtime; + uint32_t rdev_major; + uint32_t rdev_minor; + uint32_t dev_major; + uint32_t dev_minor; + uint64_t __spare2[14]; +}; + +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_CACHE) + * FOPEN_PARALLEL_DIRECT_WRITES: Allow concurrent direct writes on the same 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 supported) + * 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/trunc + * 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 request + * 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) + * FUSE_HAS_EXPIRE_ONLY: kernel supports expiry-only entry invalidation + * FUSE_DIRECT_IO_ALLOW_MMAP: allow shared mmap in FOPEN_DIRECT_IO mode. + */ +#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) +#define FUSE_HAS_EXPIRE_ONLY (1ULL << 35) +#define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36) + +/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */ +#define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP + +/** + * 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 allowed + * 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 = 31, + FUSE_EXT_GROUPS = 32, + FUSE_ERROR_IN = 33, +}; + +enum fuse_opcode { + FUSE_LOOKUP = 1, + FUSE_FORGET = 2, /* no reply */ + FUSE_GETATTR = 3, + FUSE_SETATTR = 4, + FUSE_READLINK = 5, + FUSE_SYMLINK = 6, + FUSE_MKNOD = 8, + FUSE_MKDIR = 9, + FUSE_UNLINK = 10, + FUSE_RMDIR = 11, + FUSE_RENAME = 12, + FUSE_LINK = 13, + FUSE_OPEN = 14, + FUSE_READ = 15, + FUSE_WRITE = 16, + FUSE_STATFS = 17, + FUSE_RELEASE = 18, + FUSE_FSYNC = 20, + FUSE_SETXATTR = 21, + FUSE_GETXATTR = 22, + FUSE_LISTXATTR = 23, + FUSE_REMOVEXATTR = 24, + FUSE_FLUSH = 25, + FUSE_INIT = 26, + FUSE_OPENDIR = 27, + FUSE_READDIR = 28, + FUSE_RELEASEDIR = 29, + FUSE_FSYNCDIR = 30, + FUSE_GETLK = 31, + FUSE_SETLK = 32, + FUSE_SETLKW = 33, + FUSE_ACCESS = 34, + FUSE_CREATE = 35, + FUSE_INTERRUPT = 36, + FUSE_BMAP = 37, + FUSE_DESTROY = 38, + FUSE_IOCTL = 39, + FUSE_POLL = 40, + FUSE_NOTIFY_REPLY = 41, + FUSE_BATCH_FORGET = 42, + FUSE_FALLOCATE = 43, + FUSE_READDIRPLUS = 44, + FUSE_RENAME2 = 45, + FUSE_LSEEK = 46, + FUSE_COPY_FILE_RANGE = 47, + FUSE_SETUPMAPPING = 48, + FUSE_REMOVEMAPPING = 49, + FUSE_SYNCFS = 50, + FUSE_TMPFILE = 51, + FUSE_STATX = 52, + + /* CUSE specific operations */ + CUSE_INIT = 4096, + + /* Reserved opcodes: helpful to detect structure endian-ness */ + CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */ + FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */ +}; + +enum fuse_notify_code { + FUSE_NOTIFY_POLL = 1, + FUSE_NOTIFY_INVAL_INODE = 2, + FUSE_NOTIFY_INVAL_ENTRY = 3, + FUSE_NOTIFY_STORE = 4, + FUSE_NOTIFY_RETRIEVE = 5, + FUSE_NOTIFY_DELETE = 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 = 1, + FUSE_ENTRY_BPF = 2, + FUSE_ENTRY_REMOVE_BACKING = 3, + FUSE_ENTRY_REMOVE_BPF = 4, +}; + +#define BPF_FUSE_NAME_MAX 15 + +struct fuse_bpf_entry_out { + uint32_t entry_type; + uint32_t unused; + union { + struct { + uint64_t unused2; + uint64_t fd; + }; + char name[BPF_FUSE_NAME_MAX + 1]; + }; +}; + +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; +}; + +struct fuse_statx_in { + uint32_t getattr_flags; + uint32_t reserved; + uint64_t fh; + uint32_t sx_flags; + uint32_t sx_mask; +}; + +struct fuse_statx_out { + uint64_t attr_valid; /* Cache timeout for the attributes */ + uint32_t attr_valid_nsec; + uint32_t flags; + uint64_t spare[2]; + struct fuse_statx stat; +}; + +#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; +}; + +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 context + * 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 */ From patchwork Fri Mar 29 01:53:50 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609913 Received: from mail-yw1-f201.google.com (mail-yw1-f201.google.com [209.85.128.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7E88613BC28 for ; Fri, 29 Mar 2024 01:55:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677329; cv=none; b=i61viTUzGe32Vk0m1VNLVPIuCIIi7bsacvgM1viSYHqJbDwXF6BRnv7H+cz0Zp5tNgLZ+9lsiUprz3iqpU+Y+3ygOUPx4C3tl3zwYyiU0i1Uw3bibmjs4rm+z8jTl0/iuPtOBupn6ZnHGibUsvcC7UoDJQ3lvoq04n+i0oX/aOA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677329; c=relaxed/simple; bh=zndw1+p8QwbF5PmXC/NXcskRa864acO/sGWDwAXX9Y4=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=PYOH/NYk3/iGnj2lzYEaFmvvRJJkzIUmHOr28PzwYTJ9bMrDk+wBx24hly0Ng/Ed2iGGOU9L8KUYrx6v60QR4RUOzs0ZGyatuOunfviegc2LOBjDNfRR8m4kVI4DyhSe7HwYrE2rVwiUl9RhZd6AkL2X0GMfBiuGLfY7cpxDKVI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=tB3V0LsU; arc=none smtp.client-ip=209.85.128.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="tB3V0LsU" Received: by mail-yw1-f201.google.com with SMTP id 00721157ae682-60a20c33f06so20571687b3.2 for ; Thu, 28 Mar 2024 18:55:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677317; x=1712282117; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=7KP299CLRr6vuf+6zYwNRNzJ4OYt2CpZgwuzda4ze5Q=; b=tB3V0LsUlQvvK3GnGCRY8Nf2jJ1rd+kXNbdo+VvzxXlh6ZSCa+1tqJWdT6nVXd7GAr cV0CU2eAUFhvVPHscdwIjgYzc0q8iTkcFf3LLx6+Oyvghoj4/m1zGaKwtP1ddxs1oNkM 3WP/86fswpKUwcgqkwjz+shG/2wDxoNo2Fg8uwM0H+k3MJG5qof75YblXlLEOOH11Mxa dPSp9XjThAhjWDbsud5FtbQi3G2GBjKu8hpf5JFcK+L3ZIjlWn+57KUY0iOx+31VzyNm hHZ0ictw1fyKv3Dq6izchsBs7FH1NTa8hAf35Qe2VfwIe+nylSzSsAmytbuW0RA1orcO mhng== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677317; x=1712282117; 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=7KP299CLRr6vuf+6zYwNRNzJ4OYt2CpZgwuzda4ze5Q=; b=M06QzXVS4EV3VD3Ga31tg4/mwehFsXDMXohIV5FAcjms+7lvtDlUKWzfYeQ9PjDBga Gb9FpPFpSk2Kw4pP/V8vxLJtxNu6xuQix33sTOQ5og6pjk5XHMLTcdFr1mQMeZp6ZmAC obPtBClVN5MAW4t0EtEgxRIhM064oYBk2aNrVkwa4M1pIjYV5MwcVMe2BckITok69GEF ci5J7MUqkGPFL1B8h5Vjkg6wYyjdUgFcNdrfMUs4xD5m40Z6UVhNSQue0CTNmwMULd6f UBCwj/jD5jtzvHIk+fLOAMc2KJa7LUNiuhpSmAtdwDB7M74aSwNPprkpf1lZwwwTPBXW uxNQ== X-Forwarded-Encrypted: i=1; AJvYcCVZl/sq8Din3gHin1RYZ999uQAfMbOVIPumLtgDIuBOPiOkzbXpvKHFz/axaNjfAKZRt3v0dV201VcVor6XeTFXaoYr X-Gm-Message-State: AOJu0YzO+f6R5uiFTg0gKiakadx8reQgWQCkh27Fo/nNaYxsq1feyGxu q8Vy3Mhgu7uzQpb2B8xZgFSG2mKCJKHJBU5J9gLe62+KYwVScGICSaFcYqVf5T1//+lTh/opdfx dww== X-Google-Smtp-Source: AGHT+IER1vw1g4SXSdVW/PPCNk8GjY/SdMU1H1wBZ6Zv0i/G1qqE1tvi0NDy5kQVZwGjaH7jprR6wZxm8oU= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a0d:eb47:0:b0:60a:6a5d:7db0 with SMTP id u68-20020a0deb47000000b0060a6a5d7db0mr269936ywe.0.1711677317658; Thu, 28 Mar 2024 18:55:17 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:50 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-36-drosen@google.com> Subject: [RFC PATCH v4 35/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence , Alessio Balsini X-Patchwork-State: RFC 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 | 2476 +++++++++++++++++ .../selftests/filesystems/fuse/test.bpf.c | 1045 +++++++ .../filesystems/fuse/test_framework.h | 172 ++ .../selftests/filesystems/fuse/test_fuse.h | 494 ++++ 12 files changed, 5745 insertions(+) create mode 100644 tools/testing/selftests/filesystems/fuse/.gitignore create mode 100644 tools/testing/selftests/filesystems/fuse/Makefile create mode 100644 tools/testing/selftests/filesystems/fuse/OWNERS create mode 100644 tools/testing/selftests/filesystems/fuse/bpf_common.h create mode 100644 tools/testing/selftests/filesystems/fuse/bpf_loader.c create mode 100644 tools/testing/selftests/filesystems/fuse/fd.txt create mode 100644 tools/testing/selftests/filesystems/fuse/fd_bpf.bpf.c create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_daemon.c create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_test.c create mode 100644 tools/testing/selftests/filesystems/fuse/test.bpf.c create mode 100644 tools/testing/selftests/filesystems/fuse/test_framework.h create mode 100644 tools/testing/selftests/filesystems/fuse/test_fuse.h diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/testing/selftests/filesystems/fuse/.gitignore new file mode 100644 index 000000000000..3ee9a27fe66a --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/.gitignore @@ -0,0 +1,2 @@ +fuse_test +*.raw diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile new file mode 100644 index 000000000000..b2df4dec0651 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/Makefile @@ -0,0 +1,188 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../../../../build/Build.include +include ../../../../scripts/Makefile.arch +include ../../../../scripts/Makefile.include + +#if 0 +ifneq ($(LLVM),) +ifneq ($(filter %/,$(LLVM)),) +LLVM_PREFIX := $(LLVM) +else ifneq ($(filter -%,$(LLVM)),) +LLVM_SUFFIX := $(LLVM) +endif + +CLANG_TARGET_FLAGS_arm := arm-linux-gnueabi +CLANG_TARGET_FLAGS_arm64 := aarch64-linux-gnu +CLANG_TARGET_FLAGS_hexagon := hexagon-linux-musl +CLANG_TARGET_FLAGS_m68k := m68k-linux-gnu +CLANG_TARGET_FLAGS_mips := mipsel-linux-gnu +CLANG_TARGET_FLAGS_powerpc := powerpc64le-linux-gnu +CLANG_TARGET_FLAGS_riscv := riscv64-linux-gnu +CLANG_TARGET_FLAGS_s390 := s390x-linux-gnu +CLANG_TARGET_FLAGS_x86 := x86_64-linux-gnu +CLANG_TARGET_FLAGS := $(CLANG_TARGET_FLAGS_$(ARCH)) +#endif + +ifeq ($(CROSS_COMPILE),) +ifeq ($(CLANG_TARGET_FLAGS),) +$(error Specify CROSS_COMPILE or add '--target=' option to lib.mk +else +CLANG_FLAGS += --target=$(CLANG_TARGET_FLAGS) +endif # CLANG_TARGET_FLAGS +else +CLANG_FLAGS += --target=$(notdir $(CROSS_COMPILE:%-=%)) +endif # CROSS_COMPILE + +CC := $(LLVM_PREFIX)clang$(LLVM_SUFFIX) $(CLANG_FLAGS) -fintegrated-as +else +CC := $(CROSS_COMPILE)gcc +endif # LLVM + +CURDIR := $(abspath .) +TOOLSDIR := $(abspath ../../../..) +LIBDIR := $(TOOLSDIR)/lib +BPFDIR := $(LIBDIR)/bpf +TOOLSINCDIR := $(TOOLSDIR)/include +BPFTOOLDIR := $(TOOLSDIR)/bpf/bpftool +APIDIR := $(TOOLSINCDIR)/uapi +GENDIR := $(abspath ../../../../../include/generated) +GENHDR := $(GENDIR)/autoconf.h +SELFTESTS:=$(TOOLSDIR)/testing/selftests/ + +LDLIBS := -lpthread -lelf -lz +TEST_GEN_PROGS := fuse_test fuse_daemon +TEST_GEN_FILES := \ + test.skel.h \ + fd.sh \ + +include ../../lib.mk + +# Put after include ../../lib.mk since that changes $(TEST_GEN_PROGS) +# Otherwise you get multiple targets, this becomes the default, and it's a mess +EXTRA_SOURCES := bpf_loader.c $(OUTPUT)/test.skel.h +$(TEST_GEN_PROGS) : $(EXTRA_SOURCES) $(BPFOBJ) + +SCRATCH_DIR := $(OUTPUT)/tools +BUILD_DIR := $(SCRATCH_DIR)/build +INCLUDE_DIR := $(SCRATCH_DIR)/include +BPFOBJ := $(BUILD_DIR)/libbpf/libbpf.a +SKEL_DIR := $(OUTPUT) +ifneq ($(CROSS_COMPILE),) +HOST_BUILD_DIR := $(BUILD_DIR)/host +HOST_SCRATCH_DIR := host-tools +HOST_INCLUDE_DIR := $(HOST_SCRATCH_DIR)/include +else +HOST_BUILD_DIR := $(BUILD_DIR) +HOST_SCRATCH_DIR := $(SCRATCH_DIR) +HOST_INCLUDE_DIR := $(INCLUDE_DIR) +endif +HOST_BPFOBJ := $(HOST_BUILD_DIR)/libbpf/libbpf.a +RESOLVE_BTFIDS := $(HOST_BUILD_DIR)/resolve_btfids/resolve_btfids +DEFAULT_BPFTOOL := $(HOST_SCRATCH_DIR)/sbin/bpftool + +VMLINUX_BTF_PATHS ?= $(if $(OUTPUT),$(OUTPUT)/../../../../../vmlinux) \ + $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux) \ + ../../../../../vmlinux \ + /sys/kernel/btf/vmlinux \ + /boot/vmlinux-$(shell uname -r) +VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS)))) +ifeq ($(VMLINUX_BTF),) +$(error Cannot find a vmlinux for VMLINUX_BTF at any of "$(VMLINUX_BTF_PATHS)") +endif + +BPFTOOL ?= $(DEFAULT_BPFTOOL) + +ifneq ($(wildcard $(GENHDR)),) + GENFLAGS := -DHAVE_GENHDR +endif + +CFLAGS += -g -O2 -rdynamic -pthread -Wall -Werror $(GENFLAGS) \ + -I$(INCLUDE_DIR) -I$(GENDIR) -I$(LIBDIR) \ + -I$(TOOLSINCDIR) -I$(APIDIR) -I$(SELFTESTS) \ + -I$(SKEL_DIR) + +# Silence some warnings when compiled with clang +ifneq ($(LLVM),) +CFLAGS += -Wno-unused-command-line-argument +endif + +#LDFLAGS = -lelf -lz + +IS_LITTLE_ENDIAN = $(shell $(CC) -dM -E - &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 := $(HOST_BPFOBJ) $(LDLIBS) +$(OUTPUT)/fuse_test: LDLIBS := $(HOST_BPFOBJ) $(LDLIBS) + +$(OUTPUT)/%.bpf.o: %.bpf.c $(INCLUDE_DIR)/vmlinux.h \ + | $(BPFOBJ) + $(call msg,CLNG-BPF,,$@) + $(Q)$(CLANG) $(BPF_CFLAGS) -target bpf -c $< -o $@ + +$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o $(BPFTOOL) + $(call msg,GEN-SKEL,,$@) + $(Q)$(BPFTOOL) gen object $(<:.o=.linked1.o) $< + $(Q)$(BPFTOOL) gen object $(<:.o=.linked2.o) $(<:.o=.linked1.o) + $(Q)$(BPFTOOL) gen object $(<:.o=.linked3.o) $(<:.o=.linked2.o) + $(Q)diff $(<:.o=.linked2.o) $(<:.o=.linked3.o) + $(Q)$(BPFTOOL) gen skeleton $(<:.o=.linked3.o) name $(notdir $(<:.bpf.o=))_bpf > $@ + $(Q)$(BPFTOOL) gen subskeleton $(<:.o=.linked3.o) name $(notdir $(<:.bpf.o=))_bpf > $(@:.skel.h=.subskel.h) + +$(OUTPUT)/fd.sh: fd.txt + cp $< $@ + chmod 755 $@ diff --git a/tools/testing/selftests/filesystems/fuse/OWNERS b/tools/testing/selftests/filesystems/fuse/OWNERS new file mode 100644 index 000000000000..5eb371e1a5a3 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/OWNERS @@ -0,0 +1,2 @@ +# include OWNERS from the authoritative android-mainline branch +include kernel/common:android-mainline:/tools/testing/selftests/filesystems/incfs/OWNERS diff --git a/tools/testing/selftests/filesystems/fuse/bpf_common.h b/tools/testing/selftests/filesystems/fuse/bpf_common.h new file mode 100644 index 000000000000..dcf9efaef0f4 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/bpf_common.h @@ -0,0 +1,51 @@ +// TODO: Insert description here. (generated by drosen) + +#ifndef _BPF_COMMON_H_ +#define _BPF_COMMON_H_ + +/* Return Codes for Fuse BPF programs */ +#define BPF_FUSE_CONTINUE 0 +#define BPF_FUSE_USER 1 +#define BPF_FUSE_USER_PREFILTER 2 +#define BPF_FUSE_POSTFILTER 3 +#define BPF_FUSE_USER_POSTFILTER 4 + +enum fuse_bpf_type { + FUSE_ENTRY_BACKING = 1, + FUSE_ENTRY_BPF = 2, + FUSE_ENTRY_REMOVE_BACKING = 3, + FUSE_ENTRY_REMOVE_BPF = 4, +}; + +#define BPF_FUSE_NAME_MAX 15 +struct fuse_bpf_entry_out { + uint32_t entry_type; + uint32_t unused; + union { + struct { + uint64_t unused2; + uint64_t fd; + }; + char name[BPF_FUSE_NAME_MAX + 1]; + }; +}; + +/* Op Code Filter values for BPF Programs */ +#define FUSE_OPCODE_FILTER 0x0ffff +#define FUSE_PREFILTER 0x10000 +#define FUSE_POSTFILTER 0x20000 + +#define BPF_FUSE_NAME_MAX 15 + +#define BPF_STRUCT_OPS(type, name, args...) \ +SEC("struct_ops/"#name) \ +type BPF_PROG(name, ##args) + +/* available kfuncs for fuse_bpf */ +extern uint32_t bpf_fuse_return_len(struct fuse_buffer *ptr) __ksym; +extern void bpf_fuse_get_rw_dynptr(struct fuse_buffer *buffer, struct bpf_dynptr *dynptr, u64 size, bool copy) __ksym; +extern void bpf_fuse_get_ro_dynptr(const struct fuse_buffer *buffer, struct bpf_dynptr *dynptr) __ksym; +extern void *bpf_dynptr_slice(const struct bpf_dynptr *ptr, u32 offset, void *buffer, u32 buffer__szk) __ksym; +extern void *bpf_dynptr_slice_rdwr(const struct bpf_dynptr *ptr, u32 offset, void *buffer, u32 buffer__szk) __ksym; + +#endif /* _BPF_COMMON_H_ */ diff --git a/tools/testing/selftests/filesystems/fuse/bpf_loader.c b/tools/testing/selftests/filesystems/fuse/bpf_loader.c new file mode 100644 index 000000000000..ebcced7f9430 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/bpf_loader.c @@ -0,0 +1,597 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2021 Google LLC + */ + +#include "test_fuse.h" + +#include +#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 = {0}; + + if (!s1) + return s; + + s.s = malloc(strlen(s1) + 1); + if (!s.s) + return s; + + strcpy(s.s, s1); + return s; +} + +struct s sn(const char *s1, const char *s2) +{ + struct s s = {0}; + + if (!s1) + return s; + + s.s = malloc(s2 - s1 + 1); + if (!s.s) + return s; + + strncpy(s.s, s1, s2 - s1); + s.s[s2 - s1] = 0; + return s; +} + +int s_cmp(struct s s1, struct s s2) +{ + int result = -1; + + if (!s1.s || !s2.s) + goto out; + result = strcmp(s1.s, s2.s); +out: + free(s1.s); + free(s2.s); + return result; +} + +struct s s_cat(struct s s1, struct s s2) +{ + struct s s = {0}; + + if (!s1.s || !s2.s) + goto out; + + s.s = malloc(strlen(s1.s) + strlen(s2.s) + 1); + if (!s.s) + goto out; + + strcpy(s.s, s1.s); + strcat(s.s, s2.s); +out: + free(s1.s); + free(s2.s); + return s; +} + +struct s s_splitleft(struct s s1, char c) +{ + struct s s = {0}; + char *split; + + if (!s1.s) + return s; + + split = strchr(s1.s, c); + if (split) + s = sn(s1.s, split); + + free(s1.s); + return s; +} + +struct s s_splitright(struct s s1, char c) +{ + struct s s2 = {0}; + char *split; + + if (!s1.s) + return s2; + + split = strchr(s1.s, c); + if (split) + s2 = s(split + 1); + + free(s1.s); + return s2; +} + +struct s s_word(struct s s1, char c, size_t n) +{ + while (n--) + s1 = s_splitright(s1, c); + return s_splitleft(s1, c); +} + +struct s s_path(struct s s1, struct s s2) +{ + return s_cat(s_cat(s1, s("/")), s2); +} + +struct s s_pathn(size_t n, struct s s1, ...) +{ + va_list argp; + + va_start(argp, s1); + while (--n) + s1 = s_path(s1, va_arg(argp, struct s)); + va_end(argp); + return s1; +} + +int s_link(struct s src_pathname, struct s dst_pathname) +{ + int res; + + if (src_pathname.s && dst_pathname.s) { + res = link(src_pathname.s, dst_pathname.s); + } else { + res = -1; + errno = ENOMEM; + } + + free(src_pathname.s); + free(dst_pathname.s); + return res; +} + +int s_symlink(struct s src_pathname, struct s dst_pathname) +{ + int res; + + if (src_pathname.s && dst_pathname.s) { + res = symlink(src_pathname.s, dst_pathname.s); + } else { + res = -1; + errno = ENOMEM; + } + + free(src_pathname.s); + free(dst_pathname.s); + return res; +} + + +int s_mkdir(struct s pathname, mode_t mode) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = mkdir(pathname.s, mode); + free(pathname.s); + return res; +} + +int s_rmdir(struct s pathname) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = rmdir(pathname.s); + free(pathname.s); + return res; +} + +int s_unlink(struct s pathname) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = unlink(pathname.s); + free(pathname.s); + return res; +} + +int s_open(struct s pathname, int flags, ...) +{ + va_list ap; + int res; + + va_start(ap, flags); + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + if (flags & (O_CREAT | O_TMPFILE)) + res = open(pathname.s, flags, va_arg(ap, mode_t)); + else + res = open(pathname.s, flags); + + free(pathname.s); + va_end(ap); + return res; +} + +int s_openat(int dirfd, struct s pathname, int flags, ...) +{ + va_list ap; + int res; + + va_start(ap, flags); + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + if (flags & (O_CREAT | O_TMPFILE)) + res = openat(dirfd, pathname.s, flags, va_arg(ap, mode_t)); + else + res = openat(dirfd, pathname.s, flags); + + free(pathname.s); + va_end(ap); + return res; +} + +int s_creat(struct s pathname, mode_t mode) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = open(pathname.s, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode); + free(pathname.s); + return res; +} + +int s_mkfifo(struct s pathname, mode_t mode) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = mknod(pathname.s, S_IFIFO | mode, 0); + free(pathname.s); + return res; +} + +int s_stat(struct s pathname, struct stat *st) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = stat(pathname.s, st); + free(pathname.s); + return res; +} + +int s_statfs(struct s pathname, struct statfs *st) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = statfs(pathname.s, st); + free(pathname.s); + return res; +} + +DIR *s_opendir(struct s pathname) +{ + DIR *res; + + res = opendir(pathname.s); + free(pathname.s); + return res; +} + +int s_getxattr(struct s pathname, const char name[], void *value, size_t size, + ssize_t *ret_size) +{ + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + *ret_size = getxattr(pathname.s, name, value, size); + free(pathname.s); + return *ret_size >= 0 ? 0 : -1; +} + +int s_listxattr(struct s pathname, void *list, size_t size, ssize_t *ret_size) +{ + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + *ret_size = listxattr(pathname.s, list, size); + free(pathname.s); + return *ret_size >= 0 ? 0 : -1; +} + +int s_setxattr(struct s pathname, const char name[], const void *value, size_t size, int flags) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = setxattr(pathname.s, name, value, size, flags); + free(pathname.s); + return res; +} + +int s_removexattr(struct s pathname, const char name[]) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = removexattr(pathname.s, name); + free(pathname.s); + return res; +} + +int s_rename(struct s oldpathname, struct s newpathname) +{ + int res; + + if (!oldpathname.s || !newpathname.s) { + errno = ENOMEM; + return -1; + } + + res = rename(oldpathname.s, newpathname.s); + free(oldpathname.s); + free(newpathname.s); + return res; +} + +int s_fuse_attr(struct s pathname, struct fuse_attr *fuse_attr_out) +{ + + struct stat st; + int result = TEST_FAILURE; + + TESTSYSCALL(s_stat(pathname, &st)); + + fuse_attr_out->ino = st.st_ino; + fuse_attr_out->mode = st.st_mode; + fuse_attr_out->nlink = st.st_nlink; + fuse_attr_out->uid = st.st_uid; + fuse_attr_out->gid = st.st_gid; + fuse_attr_out->rdev = st.st_rdev; + fuse_attr_out->size = st.st_size; + fuse_attr_out->blksize = st.st_blksize; + fuse_attr_out->blocks = st.st_blocks; + fuse_attr_out->atime = st.st_atime; + fuse_attr_out->mtime = st.st_mtime; + fuse_attr_out->ctime = st.st_ctime; + fuse_attr_out->atimensec = UINT32_MAX; + fuse_attr_out->mtimensec = UINT32_MAX; + fuse_attr_out->ctimensec = UINT32_MAX; + + result = TEST_SUCCESS; +out: + return result; +} + +struct s tracing_folder(void) +{ + struct s trace = {0}; + FILE *mounts = NULL; + char *line = NULL; + size_t size = 0; + + TEST(mounts = fopen("/proc/mounts", "re"), mounts); + while (getline(&line, &size, mounts) != -1) { + if (!s_cmp(s_word(sn(line, line + size), ' ', 2), + s("tracefs"))) { + trace = s_word(sn(line, line + size), ' ', 1); + break; + } + + if (!s_cmp(s_word(sn(line, line + size), ' ', 2), s("debugfs"))) + trace = s_path(s_word(sn(line, line + size), ' ', 1), + s("tracing")); + } + +out: + free(line); + fclose(mounts); + return trace; +} + +int tracing_on(void) +{ + int result = TEST_FAILURE; + int tracing_on = -1; + + TEST(tracing_on = s_open(s_path(tracing_folder(), s("tracing_on")), + O_WRONLY | O_CLOEXEC), + tracing_on != -1); + TESTEQUAL(write(tracing_on, "1", 1), 1); + result = TEST_SUCCESS; +out: + close(tracing_on); + return result; +} + +char *concat_file_name(const char *dir, const char *file) +{ + char full_name[FILENAME_MAX] = ""; + + if (snprintf(full_name, ARRAY_SIZE(full_name), "%s/%s", dir, file) < 0) + return NULL; + return strdup(full_name); +} + +char *setup_mount_dir(const char *name) +{ + struct stat st; + char *current_dir = getcwd(NULL, 0); + char *mount_dir = concat_file_name(current_dir, name); + + free(current_dir); + if (stat(mount_dir, &st) == 0) { + if (S_ISDIR(st.st_mode)) + return mount_dir; + + ksft_print_msg("%s is a file, not a dir.\n", mount_dir); + return NULL; + } + + if (mkdir(mount_dir, 0777)) { + ksft_print_msg("Can't create mount dir."); + return NULL; + } + + return mount_dir; +} + +int delete_dir_tree(const char *dir_path, bool remove_root) +{ + DIR *dir = NULL; + struct dirent *dp; + int result = 0; + + dir = opendir(dir_path); + if (!dir) { + result = -errno; + goto out; + } + + while ((dp = readdir(dir))) { + char *full_path; + + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) + continue; + + full_path = concat_file_name(dir_path, dp->d_name); + if (dp->d_type == DT_DIR) + result = delete_dir_tree(full_path, true); + else + result = unlink(full_path); + free(full_path); + if (result) + goto out; + } + +out: + if (dir) + closedir(dir); + if (!result && remove_root) + rmdir(dir_path); + return result; +} + +static int mount_fuse_maybe_init(const char *mount_dir, const char *bpf_name, int dir_fd, + int *fuse_dev_ptr, bool init) +{ + int result = TEST_FAILURE; + int fuse_dev = -1; + char options[FILENAME_MAX]; + uint8_t bytes_in[FUSE_MIN_READ_BUFFER]; + uint8_t bytes_out[FUSE_MIN_READ_BUFFER]; + + DECL_FUSE_IN(init); + + TEST(fuse_dev = open("/dev/fuse", O_RDWR | O_CLOEXEC), fuse_dev != -1); + snprintf(options, FILENAME_MAX, "fd=%d,user_id=0,group_id=0,rootmode=0040000", + fuse_dev); + if (bpf_name != NULL) + snprintf(options + strlen(options), + sizeof(options) - strlen(options), + ",root_bpf=%s", bpf_name); + if (dir_fd != -1) + snprintf(options + strlen(options), + sizeof(options) - strlen(options), + ",root_dir=%d", dir_fd); + TESTSYSCALL(mount("ABC", mount_dir, "fuse", 0, options)); + + if (init) { + TESTFUSEIN(FUSE_INIT, init_in); + TESTEQUAL(init_in->major, FUSE_KERNEL_VERSION); + TESTEQUAL(init_in->minor, FUSE_KERNEL_MINOR_VERSION); + TESTFUSEOUT1(fuse_init_out, ((struct fuse_init_out) { + .major = FUSE_KERNEL_VERSION, + .minor = FUSE_KERNEL_MINOR_VERSION, + .max_readahead = 4096, + .flags = 0, + .max_background = 0, + .congestion_threshold = 0, + .max_write = 4096, + .time_gran = 1000, + .max_pages = 12, + .map_alignment = 4096, + })); + } + + *fuse_dev_ptr = fuse_dev; + fuse_dev = -1; + result = TEST_SUCCESS; +out: + close(fuse_dev); + return result; +} + +int mount_fuse(const char *mount_dir, const char * bpf_name, int dir_fd, int *fuse_dev_ptr) +{ + return mount_fuse_maybe_init(mount_dir, bpf_name, dir_fd, fuse_dev_ptr, + true); +} + +int mount_fuse_no_init(const char *mount_dir, const char * bpf_name, int dir_fd, + int *fuse_dev_ptr) +{ + return mount_fuse_maybe_init(mount_dir, bpf_name, dir_fd, fuse_dev_ptr, + false); +} + diff --git a/tools/testing/selftests/filesystems/fuse/fd.txt b/tools/testing/selftests/filesystems/fuse/fd.txt new file mode 100644 index 000000000000..15ce77180d55 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fd.txt @@ -0,0 +1,21 @@ +fuse_daemon $* +cd fd-dst +ls +cd show +ls +fsstress -s 123 -d . -p 4 -n 100 -l5 +echo test > wibble +ls +cat wibble +fallocate -l 1000 wobble +mkdir testdir +mkdir tmpdir +rmdir tmpdir +touch tmp +mv tmp tmp2 +rm tmp2 + +# FUSE_LINK +echo "ln_src contents" > ln_src +ln ln_src ln_link +cat ln_link diff --git a/tools/testing/selftests/filesystems/fuse/fd_bpf.bpf.c b/tools/testing/selftests/filesystems/fuse/fd_bpf.bpf.c new file mode 100644 index 000000000000..9b6377b96a6e --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fd_bpf.bpf.c @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +// Copyright (c) 2021 Google LLC + +//#define __EXPORTED_HEADERS__ +//#define __KERNEL__ + +//#include +//#include + +#include "vmlinux.h" +#include +#include +#include +#include + +#include "bpf_common.h" + +char _license[] SEC("license") = "GPL"; + +#if 0 +struct fuse_bpf_map { + int map_type; + int key_size; + int value_size; + int max_entries; +}; +SEC("dummy") + +inline int strcmp(const char *a, const char *b) +{ + int i; + + for (i = 0; i < __builtin_strlen(b) + 1; ++i) + if (a[i] != b[i]) + return -1; + + return 0; +} + +SEC("maps") struct fuse_bpf_map test_map = { + BPF_MAP_TYPE_ARRAY, + sizeof(uint32_t), + sizeof(uint32_t), + 1000, +}; + +SEC("maps") struct fuse_bpf_map test_map2 = { + BPF_MAP_TYPE_HASH, + sizeof(uint32_t), + sizeof(uint64_t), + 76, +}; + +SEC("test_daemon") + +int trace_daemon(struct __bpf_fuse_args *fa) +{ + uint64_t uid_gid = bpf_get_current_uid_gid(); + uint32_t uid = uid_gid & 0xffffffff; + uint64_t pid_tgid = bpf_get_current_pid_tgid(); + uint32_t pid = pid_tgid & 0xffffffff; + uint32_t key = 23; + uint32_t *pvalue; + + + pvalue = bpf_map_lookup_elem(&test_map, &key); + if (pvalue) { + uint32_t value = *pvalue; + + bpf_printk("pid %u uid %u value %u", pid, uid, value); + value++; + bpf_map_update_elem(&test_map, &key, &value, BPF_ANY); + } + + switch (fa->opcode) { +#endif +BPF_STRUCT_OPS(uint32_t, trace_access_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_access_in *in) +{ + bpf_printk("Access: %d", meta->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_getattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_getattr_in *in) +{ + bpf_printk("Get Attr %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_setattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_setattr_in *in) +{ + bpf_printk("Set Attr %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_opendir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_open_in *in) +{ + bpf_printk("Open Dir: %d", meta->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_readdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + bpf_printk("Read Dir: fh: %lu", in->fh, in->offset); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_lookup_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("Lookup: %lx %s", meta->nodeid, name_buf); + if (meta->nodeid == 1) + return BPF_FUSE_USER_PREFILTER; + else + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_mknod_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_mknod_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("mknod %s %x %x", name_buf, in->rdev | in->mode, in->umask); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_mkdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_mkdir_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("mkdir: %s %x %x", name_buf, in->mode, in->umask); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_rmdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("rmdir: %s", name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_rename_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_rename_in *in, struct fuse_buffer *old_name, + struct fuse_buffer *new_name) +{ + struct bpf_dynptr old_name_ptr; + struct bpf_dynptr new_name_ptr; + char old_name_buf[255]; + //char new_name_buf[255]; + + bpf_fuse_get_ro_dynptr(old_name, &old_name_ptr); + //bpf_fuse_get_ro_dynptr(new_name, &new_name_ptr); + bpf_dynptr_read(old_name_buf, 255, &old_name_ptr, 0, 0); + //bpf_dynptr_read(new_name_buf, 255, &new_name_ptr, 0, 0); + bpf_printk("rename from %s", old_name_buf); + //bpf_printk("rename to %s", new_name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_rename2_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_rename2_in *in, struct fuse_buffer *old_name, + struct fuse_buffer *new_name) +{ + struct bpf_dynptr old_name_ptr; + //struct bpf_dynptr new_name_ptr; + char old_name_buf[255]; + //char new_name_buf[255]; + + bpf_fuse_get_ro_dynptr(old_name, &old_name_ptr); + //bpf_fuse_get_ro_dynptr(new_name, &new_name_ptr); + bpf_dynptr_read(old_name_buf, 255, &old_name_ptr, 0, 0); + //bpf_dynptr_read(new_name_buf, 255, &new_name_ptr, 0, 0); + bpf_printk("rename(%x) from %s", in->flags, old_name_buf); + //bpf_printk("rename to %s", new_name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_unlink_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("unlink: %s", name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_link_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_link_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char dst_name[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(dst_name, 255, &name_ptr, 0, 0); + bpf_printk("Link: %d %s", in->oldnodeid, dst_name); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_symlink_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name, struct fuse_buffer *path) +{ + struct bpf_dynptr name_ptr; + //struct bpf_dynptr path_ptr; + char link_name[255]; + //char link_path[4096]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + //bpf_fuse_get_ro_dynptr(path, &path_ptr); + bpf_dynptr_read(link_name, 255, &name_ptr, 0, 0); + //bpf_dynptr_read(link_path, 4096, &path_ptr, 0, 0); + + bpf_printk("symlink from %s", link_name); + //bpf_printk("symlink to %s", link_path); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_get_link_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char link_name[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(link_name, 255, &name_ptr, 0, 0); + bpf_printk("readlink from %s", link_name); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_release_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_release_in *in) +{ + bpf_printk("Release: %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_releasedir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_release_in *in) +{ + bpf_printk("Release Dir: %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_create_open_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_create_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("Create %s", name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_open_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_open_in *in) +{ + bpf_printk("Open: %d", meta->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_read_iter_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + bpf_printk("Read: fh: %lu, offset %lu, size %lu", + in->fh, in->offset, in->size); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_write_iter_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_write_in *in) +{ + bpf_printk("Write: fh: %lu, offset %lu, size %lu", + in->fh, in->offset, in->size); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_flush_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_flush_in *in) +{ + bpf_printk("Flush %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_file_fallocate_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_fallocate_in *in) +{ + bpf_printk("Fallocate %d %lu", in->fh, in->length); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_getxattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_getxattr_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("Getxattr %d %s", meta->nodeid, name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_listxattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_getxattr_in *in) +{ + bpf_printk("Listxattr %d %d", meta->nodeid, in->size); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_setxattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_setxattr_in *in, struct fuse_buffer *name, + struct fuse_buffer *value) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("Setxattr %d %s", meta->nodeid, name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_statfs_prefilter, const struct bpf_fuse_meta_info *meta) +{ + bpf_printk("statfs %d", meta->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_lseek_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_lseek_in *in) +{ + bpf_printk("lseek type:%d, offset:%lld", in->whence, in->offset); + return BPF_FUSE_CONTINUE; +} + +SEC(".struct_ops") +struct fuse_ops trace_ops = { + .open_prefilter = (void *)trace_open_prefilter, + .opendir_prefilter = (void *)trace_opendir_prefilter, + .create_open_prefilter = (void *)trace_create_open_prefilter, + .release_prefilter = (void *)trace_release_prefilter, + .releasedir_prefilter = (void *)trace_releasedir_prefilter, + .flush_prefilter = (void *)trace_flush_prefilter, + .lseek_prefilter = (void *)trace_lseek_prefilter, + //.copy_file_range_prefilter = (void *)trace_copy_file_range_prefilter, + //.fsync_prefilter = (void *)trace_fsync_prefilter, + //.dir_fsync_prefilter = (void *)trace_dir_fsync_prefilter, + .getxattr_prefilter = (void *)trace_getxattr_prefilter, + .listxattr_prefilter = (void *)trace_listxattr_prefilter, + .setxattr_prefilter = (void *)trace_setxattr_prefilter, + //.removexattr_prefilter = (void *)trace_removexattr_prefilter, + .read_iter_prefilter = (void *)trace_read_iter_prefilter, + .write_iter_prefilter = (void *)trace_write_iter_prefilter, + .file_fallocate_prefilter = (void *)trace_file_fallocate_prefilter, + .lookup_prefilter = (void *)trace_lookup_prefilter, + .mknod_prefilter = (void *)trace_mknod_prefilter, + .mkdir_prefilter = (void *)trace_mkdir_prefilter, + .rmdir_prefilter = (void *)trace_rmdir_prefilter, + .rename2_prefilter = (void *)trace_rename2_prefilter, + .rename_prefilter = (void *)trace_rename_prefilter, + .unlink_prefilter = (void *)trace_unlink_prefilter, + .link_prefilter = (void *)trace_link_prefilter, + .getattr_prefilter = (void *)trace_getattr_prefilter, + .setattr_prefilter = (void *)trace_setattr_prefilter, + .statfs_prefilter = (void *)trace_statfs_prefilter, + .get_link_prefilter = (void *)trace_get_link_prefilter, + .symlink_prefilter = (void *)trace_symlink_prefilter, + .readdir_prefilter = (void *)trace_readdir_prefilter, + .access_prefilter = (void *)trace_access_prefilter, + .name = "trace_ops", +}; + diff --git a/tools/testing/selftests/filesystems/fuse/fuse_daemon.c b/tools/testing/selftests/filesystems/fuse/fuse_daemon.c new file mode 100644 index 000000000000..42f9f770988b --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_daemon.c @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2021 Google LLC + */ + +#include "test_fuse.h" +#include "test.skel.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +bool user_messages; +bool kernel_messages; + +static int display_trace(void) +{ + int pid = -1; + int tp = -1; + char c; + ssize_t bytes_read; + static char line[256] = {0}; + + if (!kernel_messages) + return TEST_SUCCESS; + + TEST(pid = fork(), pid != -1); + if (pid != 0) + return pid; + + TESTEQUAL(tracing_on(), 0); + TEST(tp = s_open(s_path(tracing_folder(), s("trace_pipe")), + O_RDONLY | O_CLOEXEC), tp != -1); + for (;;) { + TEST(bytes_read = read(tp, &c, sizeof(c)), + bytes_read == 1); + if (c == '\n') { + printf("%s\n", line); + line[0] = 0; + } else + sprintf(line + strlen(line), "%c", c); + } +out: + if (pid == 0) { + close(tp); + exit(TEST_FAILURE); + } + return pid; +} + +static const char *fuse_opcode_to_string(int opcode) +{ + switch (opcode & FUSE_OPCODE_FILTER) { + case FUSE_LOOKUP: + return "FUSE_LOOKUP"; + case FUSE_FORGET: + return "FUSE_FORGET"; + case FUSE_GETATTR: + return "FUSE_GETATTR"; + case FUSE_SETATTR: + return "FUSE_SETATTR"; + case FUSE_READLINK: + return "FUSE_READLINK"; + case FUSE_SYMLINK: + return "FUSE_SYMLINK"; + case FUSE_MKNOD: + return "FUSE_MKNOD"; + case FUSE_MKDIR: + return "FUSE_MKDIR"; + case FUSE_UNLINK: + return "FUSE_UNLINK"; + case FUSE_RMDIR: + return "FUSE_RMDIR"; + case FUSE_RENAME: + return "FUSE_RENAME"; + case FUSE_LINK: + return "FUSE_LINK"; + case FUSE_OPEN: + return "FUSE_OPEN"; + case FUSE_READ: + return "FUSE_READ"; + case FUSE_WRITE: + return "FUSE_WRITE"; + case FUSE_STATFS: + return "FUSE_STATFS"; + case FUSE_RELEASE: + return "FUSE_RELEASE"; + case FUSE_FSYNC: + return "FUSE_FSYNC"; + case FUSE_SETXATTR: + return "FUSE_SETXATTR"; + case FUSE_GETXATTR: + return "FUSE_GETXATTR"; + case FUSE_LISTXATTR: + return "FUSE_LISTXATTR"; + case FUSE_REMOVEXATTR: + return "FUSE_REMOVEXATTR"; + case FUSE_FLUSH: + return "FUSE_FLUSH"; + case FUSE_INIT: + return "FUSE_INIT"; + case FUSE_OPENDIR: + return "FUSE_OPENDIR"; + case FUSE_READDIR: + return "FUSE_READDIR"; + case FUSE_RELEASEDIR: + return "FUSE_RELEASEDIR"; + case FUSE_FSYNCDIR: + return "FUSE_FSYNCDIR"; + case FUSE_GETLK: + return "FUSE_GETLK"; + case FUSE_SETLK: + return "FUSE_SETLK"; + case FUSE_SETLKW: + return "FUSE_SETLKW"; + case FUSE_ACCESS: + return "FUSE_ACCESS"; + case FUSE_CREATE: + return "FUSE_CREATE"; + case FUSE_INTERRUPT: + return "FUSE_INTERRUPT"; + case FUSE_BMAP: + return "FUSE_BMAP"; + case FUSE_DESTROY: + return "FUSE_DESTROY"; + case FUSE_IOCTL: + return "FUSE_IOCTL"; + case FUSE_POLL: + return "FUSE_POLL"; + case FUSE_NOTIFY_REPLY: + return "FUSE_NOTIFY_REPLY"; + case FUSE_BATCH_FORGET: + return "FUSE_BATCH_FORGET"; + case FUSE_FALLOCATE: + return "FUSE_FALLOCATE"; + case FUSE_READDIRPLUS: + return "FUSE_READDIRPLUS"; + case FUSE_RENAME2: + return "FUSE_RENAME2"; + case FUSE_LSEEK: + return "FUSE_LSEEK"; + case FUSE_COPY_FILE_RANGE: + return "FUSE_COPY_FILE_RANGE"; + case FUSE_SETUPMAPPING: + return "FUSE_SETUPMAPPING"; + case FUSE_REMOVEMAPPING: + return "FUSE_REMOVEMAPPING"; + //case FUSE_SYNCFS: + // return "FUSE_SYNCFS"; + case CUSE_INIT: + return "CUSE_INIT"; + case CUSE_INIT_BSWAP_RESERVED: + return "CUSE_INIT_BSWAP_RESERVED"; + case FUSE_INIT_BSWAP_RESERVED: + return "FUSE_INIT_BSWAP_RESERVED"; + } + return "?"; +} + +static int parse_options(int argc, char *const *argv) +{ + signed char c; + + while ((c = getopt(argc, argv, "kuv")) != -1) + switch (c) { + case 'v': + test_options.verbose = true; + break; + + case 'u': + user_messages = true; + break; + + case 'k': + kernel_messages = true; + break; + + default: + return -EINVAL; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + int result = TEST_FAILURE; + int trace_pid = -1; + char *mount_dir = NULL; + char *src_dir = NULL; + int src_fd = -1; + int fuse_dev = -1; + //struct map_relocation *map_relocations = NULL; + //size_t map_count = 0; + //int i; + + if (geteuid() != 0) + ksft_print_msg("Not a root, might fail to mount.\n"); + TESTEQUAL(parse_options(argc, argv), 0); + + TEST(trace_pid = display_trace(), trace_pid != -1); + + delete_dir_tree("fd-src", true); + TEST(src_dir = setup_mount_dir("fd-src"), src_dir); + delete_dir_tree("fd-dst", true); + TEST(mount_dir = setup_mount_dir("fd-dst"), mount_dir); + + test_skel = test_bpf__open_and_load(); + test_link = bpf_map__attach_struct_ops(test_skel->maps.trace_ops); + + TEST(src_fd = open("fd-src", O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TESTSYSCALL(mkdirat(src_fd, "show", 0777)); + TESTSYSCALL(mkdirat(src_fd, "hide", 0777)); + + /*for (i = 0; i < map_count; ++i) + if (!strcmp(map_relocations[i].name, "test_map")) { + uint32_t key = 23; + uint32_t value = 1234; + union bpf_attr attr = { + .map_fd = map_relocations[i].fd, + .key = ptr_to_u64(&key), + .value = ptr_to_u64(&value), + .flags = BPF_ANY, + }; + TESTSYSCALL(syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, + &attr, sizeof(attr))); + } +*/ + TESTEQUAL(mount_fuse(mount_dir, "trace_ops", src_fd, &fuse_dev), 0); + + if (fork()) + return 0; + + for (;;) { + uint8_t bytes_in[FUSE_MIN_READ_BUFFER]; + uint8_t bytes_out[FUSE_MIN_READ_BUFFER] __attribute__((unused)); + struct fuse_in_header *in_header = + (struct fuse_in_header *)bytes_in; + ssize_t res = read(fuse_dev, bytes_in, sizeof(bytes_in)); + + if (res == -1) + break; + + switch (in_header->opcode) { + case FUSE_LOOKUP | FUSE_PREFILTER: { + char *name = (char *)(bytes_in + sizeof(*in_header)); + + if (user_messages) + printf("Lookup %s\n", name); + if (!strcmp(name, "hide")) + TESTFUSEOUTERROR(-ENOENT); + else { + printf("Lookup Prefilter response: %s\n", name); + TESTFUSEOUTREAD(name, strlen(name) + 1); + } + break; + } + default: + if (user_messages) { + printf("opcode is %d (%s)\n", in_header->opcode, + fuse_opcode_to_string( + in_header->opcode)); + } + break; + } + } + + result = TEST_SUCCESS; + +out: + /*for (i = 0; i < map_count; ++i) { + free(map_relocations[i].name); + close(map_relocations[i].fd); + } + free(map_relocations);*/ + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + umount2(mount_dir, MNT_FORCE); + delete_dir_tree(mount_dir, true); + free(mount_dir); + delete_dir_tree(src_dir, true); + free(src_dir); + if (trace_pid != -1) + kill(trace_pid, SIGKILL); + return result; +} diff --git a/tools/testing/selftests/filesystems/fuse/fuse_test.c b/tools/testing/selftests/filesystems/fuse/fuse_test.c new file mode 100644 index 000000000000..6d51fce34c32 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_test.c @@ -0,0 +1,2476 @@ +// 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 +#include + +static const char *ft_src = "ft-src"; +static const char *ft_dst = "ft-dst"; + +static void fill_buffer(uint8_t *data, size_t len, int file, int block) +{ + int i; + int seed = 7919 * file + block; + + for (i = 0; i < len; i++) { + seed = 1103515245 * seed + 12345; + data[i] = (uint8_t)(seed >> (i % 13)); + } +} + +static bool test_buffer(uint8_t *data, size_t len, int file, int block) +{ + int i; + int seed = 7919 * file + block; + + for (i = 0; i < len; i++) { + seed = 1103515245 * seed + 12345; + if (data[i] != (uint8_t)(seed >> (i % 13))) + return false; + } + + return true; +} + +static int create_file(int dir, struct s name, int index, size_t blocks) +{ + int result = TEST_FAILURE; + int fd = -1; + int i; + uint8_t data[PAGE_SIZE]; + + TEST(fd = s_openat(dir, name, O_CREAT | O_WRONLY, 0777), fd != -1); + for (i = 0; i < blocks; ++i) { + fill_buffer(data, PAGE_SIZE, index, i); + TESTEQUAL(write(fd, data, sizeof(data)), PAGE_SIZE); + } + TESTSYSCALL(close(fd)); + result = TEST_SUCCESS; + +out: + close(fd); + return result; +} + +static int bpf_clear_trace(void) +{ + int result = TEST_FAILURE; + int tp = -1; + + TEST(tp = s_open(s_path(tracing_folder(), s("trace")), + O_WRONLY | O_TRUNC | O_CLOEXEC), tp != -1); + + result = TEST_SUCCESS; +out: + close(tp); + return result; +} + +static int bpf_test_trace_maybe(const char *substr, bool present) +{ + int result = TEST_FAILURE; + int tp = -1; + char trace_buffer[4096] = {}; + ssize_t bytes_read; + + TEST(tp = s_open(s_path(tracing_folder(), s("trace_pipe")), + O_RDONLY | O_CLOEXEC), + tp != -1); + fcntl(tp, F_SETFL, O_NONBLOCK); + + for (;;) { + bytes_read = read(tp, trace_buffer, sizeof(trace_buffer)); + if (present) + TESTCOND(bytes_read > 0); + else if (bytes_read <= 0) { + result = TEST_SUCCESS; + break; + } + + if (test_options.verbose) + ksft_print_msg("%s\n", trace_buffer); + + if (strstr(trace_buffer, substr)) { + if (present) + result = TEST_SUCCESS; + break; + } + } +out: + close(tp); + return result; +} + +static int bpf_test_trace(const char *substr) +{ + return bpf_test_trace_maybe(substr, true); +} + +static int bpf_test_no_trace(const char *substr) +{ + return bpf_test_trace_maybe(substr, false); +} + +static int basic_test(const char *mount_dir) +{ + const char *test_name = "test"; + const char *test_data = "data"; + + int result = TEST_FAILURE; + int fuse_dev = -1; + char *filename = NULL; + int fd = -1; + int pid = -1; + int status; + + TESTEQUAL(mount_fuse(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + char data[256]; + + filename = concat_file_name(mount_dir, test_name); + TESTERR(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); + TESTEQUAL(read(fd, data, strlen(test_data)), strlen(test_data)); + TESTCOND(!strcmp(data, test_data)); + TESTSYSCALL(close(fd)); + fd = -1; + FUSE_DAEMON + DECL_FUSE_IN(open); + DECL_FUSE_IN(read); + DECL_FUSE_IN(flush); + DECL_FUSE_IN(release); + + TESTFUSELOOKUP(test_name, 0); + TESTFUSEOUT1(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = 2, + .generation = 1, + .attr.ino = 100, + .attr.size = 4, + .attr.blksize = 512, + .attr.mode = S_IFREG | 0777, + })); + + TESTFUSEIN(FUSE_OPEN, open_in); + TESTFUSEOUT1(fuse_open_out, ((struct fuse_open_out) { + .fh = 1, + .open_flags = open_in->flags, + })); + + TESTFUSEIN(FUSE_READ, read_in); + TESTFUSEOUTREAD(test_data, strlen(test_data)); + + TESTFUSEIN(FUSE_FLUSH, flush_in); + TESTFUSEOUTEMPTY(); + + TESTFUSEIN(FUSE_RELEASE, release_in); + TESTFUSEOUTEMPTY(); + FUSE_DONE + + result = TEST_SUCCESS; +out: + if (!pid) + exit(TEST_FAILURE); + close(fuse_dev); + close(fd); + free(filename); + umount(mount_dir); + return result; +} + +static int bpf_test_real(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *test_name = "real"; + const char *test_data = "Weebles wobble but they don't fall down"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + char *filename = NULL; + int fd = -1; + char read_buffer[256] = {}; + ssize_t bytes_read; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(fd = openat(src_fd, test_name, O_CREAT | O_RDWR | O_CLOEXEC, 0777), + fd != -1); + TESTEQUAL(write(fd, test_data, strlen(test_data)), strlen(test_data)); + TESTSYSCALL(close(fd)); + fd = -1; + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + filename = concat_file_name(mount_dir, test_name); + TESTERR(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); + bytes_read = read(fd, read_buffer, strlen(test_data)); + + TESTEQUAL(bytes_read, strlen(test_data)); + TESTEQUAL(strcmp(test_data, read_buffer), 0); + TESTEQUAL(bpf_test_trace("read"), 0); + result = TEST_SUCCESS; +out: + close(fuse_dev); + close(fd); + free(filename); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + + +static int bpf_test_partial(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *test_name = "partial"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + char *filename = NULL; + int fd = -1; + int pid = -1; + int status; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TESTEQUAL(create_file(src_fd, s(test_name), 1, 2), 0); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + FUSE_ACTION + uint8_t data[PAGE_SIZE]; + + TEST(filename = concat_file_name(mount_dir, test_name), + filename); + TESTERR(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); + TESTEQUAL(read(fd, data, PAGE_SIZE), PAGE_SIZE); + //TESTEQUAL(bpf_test_trace("read"), 0); + TESTCOND(test_buffer(data, PAGE_SIZE, 2, 0)); + TESTCOND(!test_buffer(data, PAGE_SIZE, 1, 0)); + TESTEQUAL(read(fd, data, PAGE_SIZE), PAGE_SIZE); + TESTCOND(test_buffer(data, PAGE_SIZE, 1, 1)); + TESTCOND(!test_buffer(data, PAGE_SIZE, 2, 1)); + TESTSYSCALL(close(fd)); + fd = -1; + FUSE_DAEMON + uint32_t *err_in; + DECL_FUSE(open); + DECL_FUSE(read); + DECL_FUSE(release); + uint8_t data[PAGE_SIZE]; + + TESTFUSEIN2_ERR_IN(FUSE_OPEN | FUSE_POSTFILTER, open_in, open_out, err_in); + TESTEQUAL(*err_in, 0); + TESTFUSEOUT1(fuse_open_out, ((struct fuse_open_out) { + .fh = 1, + .open_flags = open_in->flags, + })); + + TESTFUSEIN(FUSE_READ, read_in); + fill_buffer(data, PAGE_SIZE, 2, 0); + TESTFUSEOUTREAD(data, PAGE_SIZE); + + //TESTFUSEIN(FUSE_RELEASE, release_in); + //TESTFUSEOUTEMPTY(); + FUSE_DONE + + result = TEST_SUCCESS; +out: + if (!pid) + exit(TEST_FAILURE); + close(fuse_dev); + close(fd); + free(filename); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_attrs(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *test_name = "partial"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + char *filename = NULL; + struct stat st; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TESTEQUAL(create_file(src_fd, s(test_name), 1, 2), 0); + + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TEST(filename = concat_file_name(mount_dir, test_name), filename); + TESTSYSCALL(stat(filename, &st)); + TESTSYSCALL(chmod(filename, 0111)); + TESTSYSCALL(stat(filename, &st)); + TESTEQUAL(st.st_mode & 0777, 0111); + TESTSYSCALL(chmod(filename, 0777)); + TESTSYSCALL(stat(filename, &st)); + TESTEQUAL(st.st_mode & 0777, 0777); + TESTSYSCALL(chown(filename, 5, 6)); + TESTSYSCALL(stat(filename, &st)); + TESTEQUAL(st.st_uid, 5); + TESTEQUAL(st.st_gid, 6); + + result = TEST_SUCCESS; +out: + close(fuse_dev); + free(filename); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_readdir(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *names[] = {"real", "partial", "fake", ".", ".."}; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int pid = -1; + int status; + DIR *dir = NULL; + struct dirent *dirent; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TESTEQUAL(create_file(src_fd, s(names[0]), 1, 2), 0); + TESTEQUAL(create_file(src_fd, s(names[1]), 1, 2), 0); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + FUSE_ACTION + int i, j; + + TEST(dir = s_opendir(s(mount_dir)), dir); + TESTEQUAL(bpf_test_trace("opendir"), 0); + + for (i = 0; i < ARRAY_SIZE(names); ++i) { + TEST(dirent = readdir(dir), dirent); + + for (j = 0; j < ARRAY_SIZE(names); ++j) + if (names[j] && + strcmp(names[j], dirent->d_name) == 0) { + names[j] = NULL; + break; + } + TESTNE(j, ARRAY_SIZE(names)); + } + TEST(dirent = readdir(dir), dirent == NULL); + TESTSYSCALL(closedir(dir)); + dir = NULL; + TESTEQUAL(bpf_test_trace("readdir"), 0); + FUSE_DAEMON + struct fuse_in_header *in_header = + (struct fuse_in_header *)bytes_in; + ssize_t res = read(fuse_dev, bytes_in, sizeof(bytes_in)); + // ignore the error in extension + res -= ERR_IN_EXT_LEN; + struct fuse_read_out *read_out = + (struct fuse_read_out *) (bytes_in + + sizeof(*in_header) + + sizeof(struct fuse_read_in)); + struct fuse_dirent *fuse_dirent = + (struct fuse_dirent *) (bytes_in + res); + + TESTGE(res, sizeof(*in_header) + sizeof(struct fuse_read_in)); + TESTEQUAL(in_header->opcode, FUSE_READDIR | FUSE_POSTFILTER); + *fuse_dirent = (struct fuse_dirent) { + .ino = 100, + .off = 5, + .namelen = strlen("fake"), + .type = DT_REG, + }; + strcpy((char *)(bytes_in + res + sizeof(*fuse_dirent)), "fake"); + res += FUSE_DIRENT_ALIGN(sizeof(*fuse_dirent) + strlen("fake") + + 1); + TESTFUSEDIROUTREAD(read_out, + bytes_in + + sizeof(struct fuse_in_header) + + sizeof(struct fuse_read_in) + + sizeof(struct fuse_read_out), + res - sizeof(struct fuse_in_header) - + sizeof(struct fuse_read_in) - + sizeof(struct fuse_read_out)); + res = read(fuse_dev, bytes_in, sizeof(bytes_in)); + TESTEQUAL(res, sizeof(*in_header) + + sizeof(struct fuse_read_in) + + sizeof(struct fuse_read_out) + ERR_IN_EXT_LEN); + TESTEQUAL(in_header->opcode, FUSE_READDIR | FUSE_POSTFILTER); + TESTFUSEDIROUTREAD(read_out, bytes_in, 0); + FUSE_DONE + + result = TEST_SUCCESS; +out: + closedir(dir); + close(fuse_dev); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_redact_readdir(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *names[] = {"f1", "f2", "f3", "f4", "f5", "f6", ".", ".."}; + int num_shown = (ARRAY_SIZE(names) - 2) / 2 + 2; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int pid = -1; + int status; + DIR *dir = NULL; + struct dirent *dirent; + int i; + int count = 0; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + for (i = 0; i < ARRAY_SIZE(names) - 2; i++) + TESTEQUAL(create_file(src_fd, s(names[i]), 1, 2), 0); + + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.readdir_redact_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "readdir_redact", src_fd, &fuse_dev), 0); + + FUSE_ACTION + int j; + + TEST(dir = s_opendir(s(mount_dir)), dir); + while ((dirent = readdir(dir))) { + errno = 0; + TESTEQUAL(errno, 0); + for (j = 0; j < ARRAY_SIZE(names); ++j) + if (names[j] && + strcmp(names[j], dirent->d_name) == 0) { + names[j] = NULL; + count++; + break; + } + TESTNE(j, ARRAY_SIZE(names)); + TESTGE(num_shown, count); + } + TESTEQUAL(count, num_shown); + TESTSYSCALL(closedir(dir)); + dir = NULL; + FUSE_DAEMON + bool skip = true; + for (int i = 0; i < ARRAY_SIZE(names) + 1; i++) { + uint8_t bytes_in[FUSE_MIN_READ_BUFFER]; + uint8_t bytes_out[FUSE_MIN_READ_BUFFER]; + struct fuse_in_header *in_header = + (struct fuse_in_header *)bytes_in; + ssize_t res = read(fuse_dev, bytes_in, sizeof(bytes_in)); + int length_out = 0; + uint8_t *pos; + uint8_t *dirs_in; + uint8_t *dirs_out; + struct fuse_read_in *fuse_read_in; + struct fuse_read_out *fuse_read_out_in; + struct fuse_read_out *fuse_read_out_out; + struct fuse_dirent *fuse_dirent_in = NULL; + struct fuse_dirent *next = NULL; + bool again = false; + int dir_ent_len = 0; + + // We're ignoring the error_in extension + res -= ERR_IN_EXT_LEN; + TESTGE(res, sizeof(struct fuse_in_header) + + sizeof(struct fuse_read_in) + + sizeof(struct fuse_read_out)); + + pos = bytes_in + sizeof(struct fuse_in_header); + fuse_read_in = (struct fuse_read_in *) pos; + pos += sizeof(*fuse_read_in); + fuse_read_out_in = (struct fuse_read_out *) pos; + pos += sizeof(*fuse_read_out_in); + dirs_in = pos; + + pos = bytes_out + sizeof(struct fuse_out_header); + fuse_read_out_out = (struct fuse_read_out *) pos; + pos += sizeof(*fuse_read_out_out); + dirs_out = pos; + + if (dirs_in < bytes_in + res) { + bool is_dot; + + fuse_dirent_in = (struct fuse_dirent *) dirs_in; + is_dot = (fuse_dirent_in->namelen == 1 && + !strncmp(fuse_dirent_in->name, ".", 1)) || + (fuse_dirent_in->namelen == 2 && + !strncmp(fuse_dirent_in->name, "..", 2)); + + dir_ent_len = FUSE_DIRENT_ALIGN( + sizeof(*fuse_dirent_in) + + fuse_dirent_in->namelen); + + if (dirs_in + dir_ent_len < bytes_in + res) + next = (struct fuse_dirent *) + (dirs_in + dir_ent_len); + + if (!skip || is_dot) { + memcpy(dirs_out, fuse_dirent_in, + sizeof(struct fuse_dirent) + + fuse_dirent_in->namelen); + length_out += dir_ent_len; + } + again = ((skip && !is_dot) && next); + + if (!is_dot) + skip = !skip; + } + + fuse_read_out_out->offset = next ? next->off : + fuse_read_out_in->offset; + fuse_read_out_out->again = again; + + { + struct fuse_out_header *out_header = + (struct fuse_out_header *)bytes_out; + + *out_header = (struct fuse_out_header) { + .len = sizeof(*out_header) + + sizeof(*fuse_read_out_out) + length_out, + .unique = in_header->unique, + }; + TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), + out_header->len); + } + } + FUSE_DONE + + result = TEST_SUCCESS; +out: + closedir(dir); + close(fuse_dev); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +/* + * This test is more to show what classic fuse does with a creat in a subdir + * than a test of any new functionality + */ +static int bpf_test_creat(const char *mount_dir) +{ + const char *dir_name = "show"; + const char *file_name = "file"; + int result = TEST_FAILURE; + int fuse_dev = -1; + int pid = -1; + int status; + int fd = -1; + + TESTEQUAL(mount_fuse(mount_dir, NULL, -1, &fuse_dev), 0); + + FUSE_ACTION + TEST(fd = s_creat(s_path(s_path(s(mount_dir), s(dir_name)), + s(file_name)), + 0777), + fd != -1); + TESTSYSCALL(close(fd)); + FUSE_DAEMON + DECL_FUSE_IN(create); + DECL_FUSE_IN(release); + DECL_FUSE_IN(flush); + + TESTFUSELOOKUP(dir_name, 0); + TESTFUSEOUT1(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = 3, + .generation = 1, + .attr.ino = 100, + .attr.size = 4, + .attr.blksize = 512, + .attr.mode = S_IFDIR | 0777, + })); + + TESTFUSELOOKUP(file_name, 0); + TESTFUSEOUTERROR(-ENOENT); + + TESTFUSEINEXT(FUSE_CREATE, create_in, strlen(file_name) + 1); + TESTFUSEOUT2(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = 2, + .generation = 1, + .attr.ino = 200, + .attr.size = 4, + .attr.blksize = 512, + .attr.mode = S_IFREG, + }), + fuse_open_out, ((struct fuse_open_out) { + .fh = 1, + .open_flags = create_in->flags, + })); + + TESTFUSEIN(FUSE_FLUSH, flush_in); + TESTFUSEOUTEMPTY(); + + TESTFUSEIN(FUSE_RELEASE, release_in); + TESTFUSEOUTEMPTY(); + FUSE_DONE + + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + return result; +} + +static int bpf_test_hidden_entries(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + static const char * const dir_names[] = { + "show", + "hide", + }; + const char *file_name = "file"; + const char *data = "The quick brown fox jumps over the lazy dog\n"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int fd = -1; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TESTSYSCALL(mkdirat(src_fd, dir_names[0], 0777)); + TESTSYSCALL(mkdirat(src_fd, dir_names[1], 0777)); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_hidden_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_hidden", src_fd, &fuse_dev), 0); + + TEST(fd = s_creat(s_path(s_path(s(mount_dir), s(dir_names[0])), + s(file_name)), + 0777), + fd != -1); + TESTSYSCALL(fallocate(fd, 0, 0, 4096)); + TEST(write(fd, data, strlen(data)), strlen(data)); + TESTSYSCALL(close(fd)); + TESTEQUAL(bpf_test_trace("Create"), 0); + + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_dir(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *dir_name = "dir"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + struct stat st; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(dir_name)), 0777)); + TESTEQUAL(bpf_test_trace("mkdir"), 0); + TESTSYSCALL(s_stat(s_path(s(ft_src), s(dir_name)), &st)); + TESTSYSCALL(s_rmdir(s_path(s(mount_dir), s(dir_name)))); + TESTEQUAL(s_stat(s_path(s(ft_src), s(dir_name)), &st), -1); + TESTEQUAL(errno, ENOENT); + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_file(const char *mount_dir, bool close_first) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *file_name = "real"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int fd = -1; + struct stat st; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TEST(fd = s_creat(s_path(s(mount_dir), s(file_name)), + 0777), + fd != -1); + TESTEQUAL(bpf_test_trace("Create"), 0); + if (close_first) { + TESTSYSCALL(close(fd)); + fd = -1; + } + TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st)); + TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(file_name)))); + TESTEQUAL(bpf_test_trace("unlink"), 0); + TESTEQUAL(s_stat(s_path(s(ft_src), s(file_name)), &st), -1); + TESTEQUAL(errno, ENOENT); + if (!close_first) { + TESTSYSCALL(close(fd)); + fd = -1; + } + result = TEST_SUCCESS; +out: + close(fd); + close(fuse_dev); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_file_early_close(const char *mount_dir) +{ + return bpf_test_file(mount_dir, true); +} + +static int bpf_test_file_late_close(const char *mount_dir) +{ + return bpf_test_file(mount_dir, false); +} + +static int bpf_test_alter_errcode_bpf(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *dir_name = "dir"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + struct stat st; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_error_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_error", src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(dir_name)), 0777)); + //TESTEQUAL(bpf_test_trace("mkdir"), 0); + TESTSYSCALL(s_stat(s_path(s(ft_src), s(dir_name)), &st)); + TESTEQUAL(s_mkdir(s_path(s(mount_dir), s(dir_name)), 0777), -EPERM); + TESTSYSCALL(s_rmdir(s_path(s(mount_dir), s(dir_name)))); + TESTEQUAL(s_stat(s_path(s(ft_src), s(dir_name)), &st), -1); + TESTEQUAL(errno, ENOENT); + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_alter_errcode_userspace(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *dir_name = "doesnotexist"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int pid = -1; + int status; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_error_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_error", src_fd, &fuse_dev), 0); + + FUSE_ACTION + TESTEQUAL(s_unlink(s_path(s(mount_dir), s(dir_name))), + -1); + TESTEQUAL(errno, ENOMEM); + FUSE_DAEMON + TESTFUSELOOKUP("doesnotexist", FUSE_POSTFILTER); + TESTFUSEOUTERROR(-ENOMEM); + FUSE_DONE + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} +//TODO: Make equivalent struct_op tests +#if 0 +static int bpf_test_verifier(const char *mount_dir) +{ + int result = TEST_FAILURE; + int bpf_fd1 = -1; + int bpf_fd2 = -1; + int bpf_fd3 = -1; + + TESTEQUAL(install_elf_bpf("test_bpf.bpf.o", "test_verify", + &bpf_fd1, NULL, NULL), 0); + TESTEQUAL(install_elf_bpf_invalid("test_bpf.bpf.o", "test_verify_fail", + &bpf_fd2, NULL, NULL), 0); + TESTEQUAL(install_elf_bpf_invalid("test_bpf.bpf.o", "test_verify_fail2", + &bpf_fd3, NULL, NULL), 0); + result = TEST_SUCCESS; +out: + close(bpf_fd1); + close(bpf_fd2); + close(bpf_fd3); + return result; +} + +static int bpf_test_verifier_out_args(const char *mount_dir) +{ + int result = TEST_FAILURE; + int bpf_fd1 = -1; + int bpf_fd2 = -1; + + TESTEQUAL(install_elf_bpf_invalid("test_bpf.bpf.o", "test_verify_fail3", + &bpf_fd1, NULL, NULL), 0); + TESTEQUAL(install_elf_bpf_invalid("test_bpf.bpf.o", "test_verify_fail4", + &bpf_fd2, NULL, NULL), 0); + result = TEST_SUCCESS; +out: + close(bpf_fd1); + close(bpf_fd2); + return result; +} + +static int bpf_test_verifier_packet_invalidation(const char *mount_dir) +{ + int result = TEST_FAILURE; + int bpf_fd1 = -1; + int bpf_fd2 = -1; + + TESTEQUAL(install_elf_bpf_invalid("test_bpf.bpf.o", "test_verify_fail5", + &bpf_fd1, NULL, NULL), 0); + TESTEQUAL(install_elf_bpf("test_bpf.bpf.o", "test_verify5", + &bpf_fd2, NULL, NULL), 0); + result = TEST_SUCCESS; +out: + close(bpf_fd1); + close(bpf_fd2); + return result; +} + +static int bpf_test_verifier_nonsense_read(const char *mount_dir) +{ + int result = TEST_FAILURE; + int bpf_fd1 = -1; + + TESTEQUAL(install_elf_bpf_invalid("test_bpf.bpf.o", "test_verify_fail6", + &bpf_fd1, NULL, NULL), 0); + result = TEST_SUCCESS; +out: + close(bpf_fd1); + return result; +} +#endif + +static int bpf_test_mknod(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *file_name = "real"; + int result = TEST_FAILURE; + int src_fd = -1; + int bpf_fd = -1; + int fuse_dev = -1; + struct stat st; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_mkfifo(s_path(s(mount_dir), s(file_name)), 0777)); + TESTEQUAL(bpf_test_trace("mknod"), 0); + TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st)); + TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(file_name)))); + TESTEQUAL(bpf_test_trace("unlink"), 0); + TESTEQUAL(s_stat(s_path(s(ft_src), s(file_name)), &st), -1); + TESTEQUAL(errno, ENOENT); + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + close(bpf_fd); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_largedir(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *show = "show"; + const int files = 1000; + + int result = TEST_FAILURE; + int src_fd = -1; + int bpf_fd = -1; + int fuse_dev = -1; + int pid = -1; + int status; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.legacy_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "legacy_ops", src_fd, &fuse_dev), 0); + + FUSE_ACTION + int i; + int fd; + DIR *dir = NULL; + struct dirent *dirent; + + TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(show)), 0777)); + for (i = 0; i < files; ++i) { + char filename[NAME_MAX]; + + sprintf(filename, "%d", i); + TEST(fd = s_creat(s_path(s_path(s(mount_dir), s(show)), + s(filename)), 0777), fd != -1); + TESTSYSCALL(close(fd)); + } + + TEST(dir = s_opendir(s_path(s(mount_dir), s(show))), dir); + for (dirent = readdir(dir); dirent; dirent = readdir(dir)) + ; + closedir(dir); + FUSE_DAEMON + int i; + + for (i = 0; i < files + 2; ++i) { + TESTFUSELOOKUP(show, FUSE_PREFILTER); + TESTFUSEOUTREAD(show, 5); + } + FUSE_DONE + + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + close(bpf_fd); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_link(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *file_name = "real"; + const char *link_name = "partial"; + int result = TEST_FAILURE; + int fd = -1; + int src_fd = -1; + int bpf_fd = -1; + int fuse_dev = -1; + struct stat st; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TEST(fd = s_creat(s_path(s(mount_dir), s(file_name)), 0777), fd != -1); + TESTEQUAL(bpf_test_trace("Create"), 0); + TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st)); + + TESTSYSCALL(s_link(s_path(s(mount_dir), s(file_name)), + s_path(s(mount_dir), s(link_name)))); + + TESTEQUAL(bpf_test_trace("link"), 0); + TESTSYSCALL(s_stat(s_path(s(ft_src), s(link_name)), &st)); + + TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(link_name)))); + TESTEQUAL(bpf_test_trace("unlink"), 0); + TESTEQUAL(s_stat(s_path(s(ft_src), s(link_name)), &st), -1); + TESTEQUAL(errno, ENOENT); + + TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(file_name)))); + TESTEQUAL(bpf_test_trace("unlink"), 0); + TESTEQUAL(s_stat(s_path(s(ft_src), s(file_name)), &st), -1); + TESTEQUAL(errno, ENOENT); + + result = TEST_SUCCESS; +out: + close(fd); + close(fuse_dev); + umount(mount_dir); + close(bpf_fd); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_symlink(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *test_name = "real"; + const char *symlink_name = "partial"; + const char *test_data = "Weebles wobble but they don't fall down"; + int result = TEST_FAILURE; + int bpf_fd = -1; + int src_fd = -1; + int fuse_dev = -1; + int fd = -1; + char read_buffer[256] = {}; + ssize_t bytes_read; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(fd = openat(src_fd, test_name, O_CREAT | O_RDWR | O_CLOEXEC, 0777), + fd != -1); + TESTEQUAL(write(fd, test_data, strlen(test_data)), strlen(test_data)); + TESTSYSCALL(close(fd)); + fd = -1; + + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_symlink(s_path(s(mount_dir), s(test_name)), + s_path(s(mount_dir), s(symlink_name)))); + TESTEQUAL(bpf_test_trace("symlink"), 0); + + TESTERR(fd = s_open(s_path(s(mount_dir), s(symlink_name)), O_RDONLY | O_CLOEXEC), fd != -1); + bytes_read = read(fd, read_buffer, strlen(test_data)); + TESTEQUAL(bpf_test_trace("readlink"), 0); + TESTEQUAL(bytes_read, strlen(test_data)); + TESTEQUAL(strcmp(test_data, read_buffer), 0); + + result = TEST_SUCCESS; +out: + close(fuse_dev); + close(fd); + umount(mount_dir); + close(src_fd); + close(bpf_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_xattr(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + static const char file_name[] = "real"; + static const char xattr_name[] = "user.xattr_test_name"; + static const char xattr_value[] = "this_is_a_test"; + const size_t xattr_size = sizeof(xattr_value); + char xattr_value_ret[256]; + ssize_t xattr_size_ret; + ssize_t xattr_size_ret_se; + int result = TEST_FAILURE; + int fd = -1; + int src_fd = -1; + int bpf_fd = -1; + int fuse_dev = -1; + struct stat st; + + memset(xattr_value_ret, '\0', sizeof(xattr_value_ret)); + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + + TEST(fd = s_creat(s_path(s(mount_dir), s(file_name)), 0777), fd != -1); + TESTEQUAL(bpf_test_trace("Create"), 0); + TESTSYSCALL(close(fd)); + + TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st)); + TEST(result = s_getxattr(s_path(s(mount_dir), s(file_name)), xattr_name, + xattr_value_ret, sizeof(xattr_value_ret), + &xattr_size_ret), + result == -1); + TESTEQUAL(errno, ENODATA); + TESTEQUAL(bpf_test_trace("getxattr"), 0); + + TESTSYSCALL(s_listxattr(s_path(s(mount_dir), s(file_name)), + xattr_value_ret, sizeof(xattr_value_ret), + &xattr_size_ret_se)); + TESTEQUAL(bpf_test_trace("listxattr"), 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 - xattr_size_ret_se, sizeof(xattr_name)); + TESTEQUAL(strcmp(xattr_name, xattr_value_ret + xattr_size_ret_se), 0); + + TESTSYSCALL(s_getxattr(s_path(s(mount_dir), s(file_name)), xattr_name, + xattr_value_ret, sizeof(xattr_value_ret), + &xattr_size_ret)); + TESTEQUAL(bpf_test_trace("getxattr"), 0); + TESTEQUAL(xattr_size, xattr_size_ret); + TESTEQUAL(strcmp(xattr_value, xattr_value_ret), 0); + + TESTSYSCALL(s_removexattr(s_path(s(mount_dir), s(file_name)), xattr_name)); + TESTEQUAL(bpf_test_trace("removexattr"), 0); + + TESTEQUAL(s_getxattr(s_path(s(mount_dir), s(file_name)), xattr_name, + xattr_value_ret, sizeof(xattr_value_ret), + &xattr_size_ret), -1); + TESTEQUAL(errno, ENODATA); + + TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(file_name)))); + TESTEQUAL(bpf_test_trace("unlink"), 0); + TESTEQUAL(s_stat(s_path(s(ft_src), s(file_name)), &st), -1); + TESTEQUAL(errno, ENOENT); + + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + close(bpf_fd); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_set_backing(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *backing_name = "backing"; + const char *test_data = "data"; + const char *test_name = "test"; + + int result = TEST_FAILURE; + int fuse_dev = -1; + int fd = -1; + int pid = -1; + int status; + + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse_no_init(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + char data[256] = {0}; + + TESTERR(fd = s_open(s_path(s(mount_dir), s(test_name)), + O_RDONLY | O_CLOEXEC), fd != -1); + TESTEQUAL(read(fd, data, strlen(test_data)), strlen(test_data)); + TESTCOND(!strcmp(data, test_data)); + TESTSYSCALL(close(fd)); + fd = -1; + TESTSYSCALL(umount(mount_dir)); + FUSE_DAEMON + //int bpf_fd = -1; + int backing_fd = -1; + struct fuse_bpf_entry_out bpf_entry[2]; + + TESTERR(backing_fd = s_creat(s_path(s(ft_src), s(backing_name)), 0777), + backing_fd != -1); + TESTEQUAL(write(backing_fd, test_data, strlen(test_data)), + strlen(test_data)); + + TESTFUSEINIT(); + TESTFUSELOOKUP(test_name, 0); + bpf_entry[0] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BPF, + .name = "trace_ops", + }; + bpf_entry[1] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + }; + TESTFUSEOUT3_IOCTL(fuse_entry_out, ((struct fuse_entry_out) {0}), + fuse_bpf_entry_out, bpf_entry[0], + fuse_bpf_entry_out, bpf_entry[1]); + read(fuse_dev, bytes_in, sizeof(bytes_in)); + //TESTSYSCALL(close(bpf_fd)); + TESTSYSCALL(close(backing_fd)); + FUSE_DONE + + result = TEST_SUCCESS; +out: + if (!pid) + exit(TEST_FAILURE); + close(fuse_dev); + close(fd); + umount(mount_dir); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_set_backing_no_ioctl(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *backing_name = "backing"; + const char *test_name = "test"; + + int result = TEST_FAILURE; + int fuse_dev = -1; + int fd = -1; + int pid = -1; + int status; + + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse_no_init(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + + TESTERR(fd = s_open(s_path(s(mount_dir), s(test_name)), + O_RDONLY | O_CLOEXEC), fd == -1); + FUSE_DAEMON + int backing_fd = -1; + struct fuse_bpf_entry_out bpf_entry[2]; + + TESTERR(backing_fd = s_creat(s_path(s(ft_src), s(backing_name)), 0777), + backing_fd != -1); + + TESTFUSEINIT(); + TESTFUSELOOKUP(test_name, 0); + bpf_entry[0] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BPF, + .name = "trace_ops", + }; + bpf_entry[1] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + }; + TESTFUSEOUT3_FAIL(fuse_entry_out, ((struct fuse_entry_out) {0}), + fuse_bpf_entry_out, bpf_entry[0], + fuse_bpf_entry_out, bpf_entry[1]); + TESTSYSCALL(close(backing_fd)); + FUSE_DONE + + result = TEST_SUCCESS; +out: + if (!pid) + exit(TEST_FAILURE); + close(fuse_dev); + close(fd); + umount(mount_dir); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_set_backing_folder(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *backing_name = "backingdir"; + const char *test_name = "testdir"; + const char *names[] = {"file", ".", ".."}; + + int result = TEST_FAILURE; + int fuse_dev = -1; + int fd = -1; + int pid = -1; + int status; + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse_no_init(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + DIR *dir = NULL; + struct dirent *dirent; + int i, j; + + TEST(dir = s_opendir(s_path(s(mount_dir), s(test_name))), dir); + + for (i = 0; i < ARRAY_SIZE(names); ++i) { + TEST(dirent = readdir(dir), dirent); + + for (j = 0; j < ARRAY_SIZE(names); ++j) + if (names[j] && + strcmp(names[j], dirent->d_name) == 0) { + names[j] = NULL; + break; + } + TESTNE(j, ARRAY_SIZE(names)); + } + TEST(dirent = readdir(dir), dirent == NULL); + TESTSYSCALL(closedir(dir)); + dir = NULL; + TESTEQUAL(bpf_test_trace("Read Dir"), 0); + TESTSYSCALL(umount(mount_dir)); + FUSE_DAEMON + int backing_fd = -1; + struct fuse_bpf_entry_out bpf_entry[2]; + + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(backing_name)), 0777)); + TESTERR(backing_fd = s_open(s_path(s(ft_src), s(backing_name)), O_RDONLY | O_CLOEXEC), + backing_fd != -1); + TESTSYSCALL(s_mkdir(s_pathn(3, s(ft_src), s(backing_name), s(names[0])), 0777)); + + TESTFUSEINIT(); + TESTFUSELOOKUP(test_name, 0); + + bpf_entry[0] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BPF, + .name = "passthrough", + }; + bpf_entry[1] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + }; + bpf_entry[0] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BPF, + .name = "trace_ops", + }; + bpf_entry[1] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + }; + TESTFUSEOUT3_IOCTL(fuse_entry_out, ((struct fuse_entry_out) {0}), + fuse_bpf_entry_out, bpf_entry[0], + fuse_bpf_entry_out, bpf_entry[1]); + TESTSYSCALL(close(backing_fd)); + FUSE_DONE + + result = TEST_SUCCESS; +out: + if (!pid) + exit(TEST_FAILURE); + close(fuse_dev); + close(fd); + umount(mount_dir); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_remove_backing(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *folder1 = "folder1"; + const char *folder2 = "folder2"; + const char *file = "file1"; + const char *contents1 = "contents1"; + const char *contents2 = "contents2"; + + int result = TEST_FAILURE; + int fuse_dev = -1; + int fd = -1; + int src_fd = -1; + int pid = -1; + int status; + char data[256] = {0}; + + /* + * Create folder1/file + * folder2/file + * + * test will install bpf into mount + * bpf will postfilter root lookup to daemon + * daemon will remove bpf and redirect opens on folder1 to folder2 + * test will open folder1/file which will be redirected to folder2 + * test will check no traces for file, and contents are folder2/file + */ + TESTEQUAL(bpf_clear_trace(), 0); + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777)); + TEST(fd = s_creat(s_pathn(3, s(ft_src), s(folder1), s(file)), 0777), + fd != -1); + TESTEQUAL(write(fd, contents1, strlen(contents1)), strlen(contents1)); + TESTSYSCALL(close(fd)); + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder2)), 0777)); + TEST(fd = s_creat(s_pathn(3, s(ft_src), s(folder2), s(file)), 0777), + fd != -1); + TESTEQUAL(write(fd, contents2, strlen(contents2)), strlen(contents2)); + TESTSYSCALL(close(fd)); + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.passthrough_ops), test_link != NULL); + TESTEQUAL(mount_fuse_no_init(mount_dir, "passthrough", src_fd, &fuse_dev), 0); + + FUSE_ACTION + TESTERR(fd = s_open(s_pathn(3, s(mount_dir), s(folder1), + s(file)), + O_RDONLY | O_CLOEXEC), fd != -1); + TESTEQUAL(read(fd, data, sizeof(data)), strlen(contents2)); + TESTCOND(!strcmp(data, contents2)); + TESTEQUAL(bpf_test_no_trace("file"), 0); + TESTSYSCALL(close(fd)); + fd = -1; + TESTSYSCALL(umount(mount_dir)); + FUSE_DAEMON + // The bpf postfilter only sets one fuse_bpf_entry_out + struct in_str { + char name[8]; + struct fuse_entry_out feo; + struct fuse_bpf_entry_out febo[1]; + } __attribute__((packed)); + uint32_t *err_in; + struct in_str *in; + int backing_fd = -1; + struct fuse_bpf_entry_out bpf_entry[2]; + + TESTFUSEINIT(); + TESTFUSEIN_ERR_IN(FUSE_LOOKUP | FUSE_POSTFILTER, in, err_in); + TESTEQUAL(*err_in, 0); + TEST(backing_fd = s_open(s_path(s(ft_src), s(folder2)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd != -1); + + bpf_entry[0] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_REMOVE_BPF, + }; + bpf_entry[1] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + }; + TESTFUSEOUT3_IOCTL(fuse_entry_out, ((struct fuse_entry_out) {0}), + fuse_bpf_entry_out, bpf_entry[0], + fuse_bpf_entry_out, bpf_entry[1]); + + while (read(fuse_dev, bytes_in, sizeof(bytes_in)) != -1) + ; + TESTSYSCALL(close(backing_fd)); + FUSE_DONE + + result = TEST_SUCCESS; +out: + close(fuse_dev); + close(fd); + close(src_fd); + umount(mount_dir); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_dir_rename(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *dir_name = "dir"; + const char *dir_name2 = "dir2"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + struct stat st; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(dir_name)), 0777)); + TESTEQUAL(bpf_test_trace("mkdir"), 0); + TESTSYSCALL(s_stat(s_path(s(ft_src), s(dir_name)), &st)); + TESTSYSCALL(s_rename(s_path(s(mount_dir), s(dir_name)), + s_path(s(mount_dir), s(dir_name2)))); + TESTEQUAL(s_stat(s_path(s(ft_src), s(dir_name)), &st), -1); + TESTEQUAL(errno, ENOENT); + TESTSYSCALL(s_stat(s_path(s(ft_src), s(dir_name2)), &st)); + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_file_rename(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *dir = "dir"; + const char *file1 = "file1"; + const char *file2 = "file2"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int fd = -1; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(dir)), 0777)); + TEST(fd = s_creat(s_pathn(3, s(mount_dir), s(dir), s(file1)), 0777), + fd != -1); + TESTSYSCALL(s_rename(s_pathn(3, s(mount_dir), s(dir), s(file1)), + s_pathn(3, s(mount_dir), s(dir), s(file2)))); + result = TEST_SUCCESS; +out: + close(fd); + umount(mount_dir); + close(fuse_dev); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int mmap_test(const char *mount_dir) +{ + const char *file = "file"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int fd = -1; + char *addr = NULL; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TESTEQUAL(mount_fuse(mount_dir, NULL, src_fd, &fuse_dev), 0); + TEST(fd = s_open(s_path(s(mount_dir), s(file)), + O_CREAT | O_RDWR | O_CLOEXEC, 0777), + fd != -1); + TESTSYSCALL(fallocate(fd, 0, 4096, SEEK_CUR)); + TEST(addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0), + addr != (void *) -1); + memset(addr, 'a', 4096); + + result = TEST_SUCCESS; +out: + munmap(addr, 4096); + close(fd); + umount(mount_dir); + close(fuse_dev); + close(src_fd); + return result; +} + +static int flock_test(const char *mount_dir) +{ + const char *file = "file"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int fd = -1, fd2 = -1; + int backing_fd = -1; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TESTEQUAL(mount_fuse(mount_dir, NULL, src_fd, &fuse_dev), 0); + TEST(fd = s_open(s_path(s(mount_dir), s(file)), + O_CREAT | O_RDWR | O_CLOEXEC, 0777), + fd != -1); + TEST(fd2 = s_open(s_path(s(mount_dir), s(file)), + O_RDWR | O_CLOEXEC, 0777), + fd2 != -1); + TESTSYSCALL(flock(fd, LOCK_EX | LOCK_NB)); + TESTCONDERR((flock(fd2, LOCK_EX | LOCK_NB)) == -1); + TESTCOND(errno == EAGAIN); + TESTSYSCALL(flock(fd, LOCK_UN)); + TESTSYSCALL(flock(fd2, LOCK_EX | LOCK_NB)); + TEST(backing_fd = s_open(s_path(s(ft_src), s(file)), + O_RDONLY | O_CLOEXEC), + backing_fd != -1); + TESTCONDERR((flock(backing_fd, LOCK_EX | LOCK_NB)) == -1); + TESTCOND(errno == EAGAIN); + close(fd2); + fd2 = 0; + TESTSYSCALL(flock(backing_fd, LOCK_EX | LOCK_NB)); + + result = TEST_SUCCESS; +out: + close(fd); + close(fd2); + close(backing_fd); + umount(mount_dir); + close(fuse_dev); + close(src_fd); + return result; +} + +static int readdir_perms_test(const char *mount_dir) +{ + int result = TEST_FAILURE; + struct __user_cap_header_struct uchs = { _LINUX_CAPABILITY_VERSION_3 }; + struct __user_cap_data_struct ucds[2]; + int src_fd = -1; + int fuse_dev = -1; + DIR *dir = NULL; + + /* Must remove capabilities for this test. */ + TESTSYSCALL(syscall(SYS_capget, &uchs, ucds)); + ucds[0].effective &= ~(1 << CAP_DAC_OVERRIDE | 1 << CAP_DAC_READ_SEARCH); + TESTSYSCALL(syscall(SYS_capset, &uchs, ucds)); + + /* This is what we are testing in fuseland. First test without fuse, */ + TESTSYSCALL(mkdir("test", 0111)); + TEST(dir = opendir("test"), dir == NULL); + if (dir) + closedir(dir); + dir = NULL; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TESTEQUAL(mount_fuse(mount_dir, NULL, src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s("test")), 0111)); + TEST(dir = s_opendir(s_path(s(mount_dir), s("test"))), dir == NULL); + + result = TEST_SUCCESS; +out: + ucds[0].effective |= 1 << CAP_DAC_OVERRIDE | 1 << CAP_DAC_READ_SEARCH; + syscall(SYS_capset, &uchs, ucds); + + closedir(dir); + s_rmdir(s_path(s(mount_dir), s("test"))); + umount(mount_dir); + close(fuse_dev); + close(src_fd); + rmdir("test"); + return result; +} + +static int bpf_test_statfs(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int fd = -1; + struct statfs st; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_statfs(s(mount_dir), &st)); + TESTEQUAL(bpf_test_trace("statfs"), 0); + TESTEQUAL(st.f_type, 0x65735546); + result = TEST_SUCCESS; +out: + close(fd); + umount(mount_dir); + close(fuse_dev); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_lseek(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *file = "real"; + const char *sparse_file = "sparse"; + const off_t sparse_length = 0x100000000u; + const char *test_data = "data"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int fd = -1; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(fd = openat(src_fd, file, O_CREAT | O_RDWR | O_CLOEXEC, 0777), + fd != -1); + TESTEQUAL(write(fd, test_data, strlen(test_data)), strlen(test_data)); + TESTSYSCALL(close(fd)); + fd = -1; + TEST(fd = openat(src_fd, sparse_file, O_CREAT | O_RDWR | O_CLOEXEC, + 0777), + fd != -1); + TESTSYSCALL(ftruncate(fd, sparse_length)); + TESTSYSCALL(close(fd)); + fd = -1; + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "trace_ops", src_fd, &fuse_dev), 0); + + TEST(fd = s_open(s_path(s(mount_dir), s(file)), O_RDONLY | O_CLOEXEC), + fd != -1); + TESTEQUAL(lseek(fd, 3, SEEK_SET), 3); + TESTEQUAL(bpf_test_trace("lseek"), 0); + TESTEQUAL(lseek(fd, 5, SEEK_END), 9); + TESTEQUAL(bpf_test_trace("lseek"), 0); + TESTEQUAL(lseek(fd, 1, SEEK_CUR), 10); + TESTEQUAL(bpf_test_trace("lseek"), 0); + TESTEQUAL(lseek(fd, 1, SEEK_DATA), 1); + TESTEQUAL(bpf_test_trace("lseek"), 0); + TESTSYSCALL(close(fd)); + fd = -1; + + TEST(fd = s_open(s_path(s(mount_dir), s(sparse_file)), + O_RDONLY | O_CLOEXEC), + fd != -1); + TESTEQUAL(lseek(fd, -256, SEEK_END), sparse_length - 256); + TESTEQUAL(lseek(fd, 0, SEEK_CUR), sparse_length - 256); + + TESTSYSCALL(close(fd)); + fd = -1; + result = TEST_SUCCESS; +out: + close(fd); + umount(mount_dir); + close(fuse_dev); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +/* + * State: + * Original: dst/folder1/content.txt + * ^ + * | + * | + * Backing: src/folder1/content.txt + * + * Step 1: open(folder1) - set backing to src/folder1 + * Check 1: cat(content.txt) - check not receiving call on the fuse daemon + * and content is the same + * Step 2: readdirplus(dst) + * Check 2: cat(content.txt) - check not receiving call on the fuse daemon + * and content is the same + */ +static int bpf_test_readdirplus_not_overriding_backing(const char *mount_dir) +{ + const char *folder1 = "folder1"; + const char *content_file = "content.txt"; + const char *content = "hello world"; + + int result = TEST_FAILURE; + int fuse_dev = -1; + int src_fd = -1; + int content_fd = -1; + int pid = -1; + int status; + + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777)); + TEST(content_fd = s_creat(s_pathn(3, s(ft_src), s(folder1), s(content_file)), 0777), + content_fd != -1); + TESTEQUAL(write(content_fd, content, strlen(content)), strlen(content)); + TESTEQUAL(mount_fuse_no_init(mount_dir, NULL, -1, &fuse_dev), 0); + + FUSE_ACTION + DIR *open_mount_dir = NULL; + struct dirent *mount_dirent; + int dst_folder1_fd = -1; + int dst_content_fd = -1; + int dst_content_read_size = -1; + char content_buffer[12]; + + // Step 1: Lookup folder1 + TESTERR(dst_folder1_fd = s_open(s_path(s(mount_dir), s(folder1)), + O_RDONLY | O_CLOEXEC), dst_folder1_fd != -1); + + // Check 1: Read content file (backed) + TESTERR(dst_content_fd = + s_open(s_pathn(3, s(mount_dir), s(folder1), s(content_file)), + O_RDONLY | O_CLOEXEC), dst_content_fd != -1); + + TEST(dst_content_read_size = + read(dst_content_fd, content_buffer, strlen(content)), + dst_content_read_size == strlen(content) && + strcmp(content, content_buffer) == 0); + + TESTSYSCALL(close(dst_content_fd)); + dst_content_fd = -1; + TESTSYSCALL(close(dst_folder1_fd)); + dst_folder1_fd = -1; + memset(content_buffer, 0, strlen(content)); + + // Step 2: readdir folder 1 + TEST(open_mount_dir = s_opendir(s(mount_dir)), + open_mount_dir != NULL); + TEST(mount_dirent = readdir(open_mount_dir), mount_dirent != NULL); + TESTSYSCALL(closedir(open_mount_dir)); + open_mount_dir = NULL; + + // Check 2: Read content file again (must be backed) + TESTERR(dst_content_fd = + s_open(s_pathn(3, s(mount_dir), s(folder1), s(content_file)), + O_RDONLY | O_CLOEXEC), dst_content_fd != -1); + + TEST(dst_content_read_size = + read(dst_content_fd, content_buffer, strlen(content)), + dst_content_read_size == strlen(content) && + strcmp(content, content_buffer) == 0); + + TESTSYSCALL(close(dst_content_fd)); + dst_content_fd = -1; + FUSE_DAEMON + size_t read_size = 0; + struct fuse_in_header *in_header = (struct fuse_in_header *)bytes_in; + struct fuse_read_out *read_out = NULL; + struct fuse_attr attr = {}; + int backing_fd = -1; + DECL_FUSE_IN(open); + DECL_FUSE_IN(getattr); + + TESTFUSEINITFLAGS(FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO); + + // Step 1: Lookup folder 1 with backing + TESTFUSELOOKUP(folder1, 0); + TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr)); + TEST(backing_fd = s_open(s_path(s(ft_src), s(folder1)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd != -1); + TESTFUSEOUT2_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = attr.ino, + .generation = 0, + .entry_valid = UINT64_MAX, + .attr_valid = UINT64_MAX, + .entry_valid_nsec = UINT32_MAX, + .attr_valid_nsec = UINT32_MAX, + .attr = attr, + }), fuse_bpf_entry_out, ((struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + })); + TESTSYSCALL(close(backing_fd)); + + // Step 2: Open root dir + TESTFUSEIN(FUSE_OPENDIR, open_in); + TESTFUSEOUT1(fuse_open_out, ((struct fuse_open_out) { + .fh = 100, + .open_flags = open_in->flags + })); + + // Step 2: Handle getattr + TESTFUSEIN(FUSE_GETATTR, getattr_in); + TESTSYSCALL(s_fuse_attr(s(ft_src), &attr)); + TESTFUSEOUT1(fuse_attr_out, ((struct fuse_attr_out) { + .attr_valid = UINT64_MAX, + .attr_valid_nsec = UINT32_MAX, + .attr = attr + })); + + // Step 2: Handle readdirplus + read_size = read(fuse_dev, bytes_in, sizeof(bytes_in)); + TESTEQUAL(in_header->opcode, FUSE_READDIRPLUS); + + struct fuse_direntplus *dirent_plus = + (struct fuse_direntplus *) (bytes_in + read_size); + struct fuse_dirent dirent; + struct fuse_entry_out entry_out; + + read_out = (struct fuse_read_out *) (bytes_in + + sizeof(*in_header) + + sizeof(struct fuse_read_in)); + + TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr)); + + dirent = (struct fuse_dirent) { + .ino = attr.ino, + .off = 1, + .namelen = strlen(folder1), + .type = DT_REG + }; + entry_out = (struct fuse_entry_out) { + .nodeid = attr.ino, + .generation = 0, + .entry_valid = UINT64_MAX, + .attr_valid = UINT64_MAX, + .entry_valid_nsec = UINT32_MAX, + .attr_valid_nsec = UINT32_MAX, + .attr = attr + }; + *dirent_plus = (struct fuse_direntplus) { + .dirent = dirent, + .entry_out = entry_out + }; + + strcpy((char *)(bytes_in + read_size + sizeof(*dirent_plus)), folder1); + read_size += FUSE_DIRENT_ALIGN(sizeof(*dirent_plus) + strlen(folder1) + + 1); + TESTFUSEDIROUTREAD(read_out, + bytes_in + + sizeof(struct fuse_in_header) + + sizeof(struct fuse_read_in) + + sizeof(struct fuse_read_out), + read_size - sizeof(struct fuse_in_header) - + sizeof(struct fuse_read_in) - + sizeof(struct fuse_read_out)); + FUSE_DONE + + result = TEST_SUCCESS; + +out: + close(fuse_dev); + close(content_fd); + close(src_fd); + umount(mount_dir); + return result; +} + +static int bpf_test_no_readdirplus_without_nodeid(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *folder1 = "folder1"; + const char *folder2 = "folder2"; + int result = TEST_FAILURE; + int fuse_dev = -1; + int src_fd = -1; + int content_fd = -1; + int pid = -1; + int status; + + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.readdir_plus_ops), test_link != NULL); + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777)); + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder2)), 0777)); + TESTEQUAL(mount_fuse_no_init(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + DIR *open_dir = NULL; + struct dirent *dirent; + + // Folder 1: Readdir with no nodeid + TEST(open_dir = s_opendir(s_path(s(ft_dst), s(folder1))), + open_dir != NULL); + TEST(dirent = readdir(open_dir), dirent == NULL); + TESTCOND(errno == EINVAL); + TESTSYSCALL(closedir(open_dir)); + open_dir = NULL; + + // Folder 2: Readdir with a nodeid + TEST(open_dir = s_opendir(s_path(s(ft_dst), s(folder2))), + open_dir != NULL); + TEST(dirent = readdir(open_dir), dirent == NULL); + TESTCOND(errno == EINVAL); + TESTSYSCALL(closedir(open_dir)); + open_dir = NULL; + FUSE_DAEMON + size_t read_size; + struct fuse_in_header *in_header = (struct fuse_in_header *)bytes_in; + struct fuse_attr attr = {}; + int backing_fd = -1; + struct fuse_bpf_entry_out bpf_entry[2]; + + TESTFUSEINITFLAGS(FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO); + + // folder 1: Set 0 as nodeid, Expect READDIR + TESTFUSELOOKUP(folder1, 0); + TEST(backing_fd = s_open(s_path(s(ft_src), s(folder1)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd != -1); + + bpf_entry[0] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BPF, + .name = "readdir_plus", + }; + bpf_entry[1] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + }; + TESTFUSEOUT3_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = 0, + .generation = 0, + .entry_valid = UINT64_MAX, + .attr_valid = UINT64_MAX, + .entry_valid_nsec = UINT32_MAX, + .attr_valid_nsec = UINT32_MAX, + .attr = attr, + }), fuse_bpf_entry_out, bpf_entry[0], + fuse_bpf_entry_out, bpf_entry[1]); + TESTSYSCALL(close(backing_fd)); + TEST(read_size = read(fuse_dev, bytes_in, sizeof(bytes_in)), read_size > 0); + TESTEQUAL(in_header->opcode, FUSE_READDIR); + TESTFUSEOUTERROR(-EINVAL); + + // folder 2: Set 10 as nodeid, Expect READDIRPLUS + TESTFUSELOOKUP(folder2, 0); + TEST(backing_fd = s_open(s_path(s(ft_src), s(folder2)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd != -1); + bpf_entry[0] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BPF, + .name = "readdir_plus", + }; + bpf_entry[1] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + }; + TESTFUSEOUT3_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = 10, + .generation = 0, + .entry_valid = UINT64_MAX, + .attr_valid = UINT64_MAX, + .entry_valid_nsec = UINT32_MAX, + .attr_valid_nsec = UINT32_MAX, + .attr = attr, + }), fuse_bpf_entry_out, bpf_entry[0], + fuse_bpf_entry_out, bpf_entry[1]); + TESTSYSCALL(close(backing_fd)); + TEST(read_size = read(fuse_dev, bytes_in, sizeof(bytes_in)), read_size > 0); + TESTEQUAL(in_header->opcode, FUSE_READDIRPLUS); + TESTFUSEOUTERROR(-EINVAL); + FUSE_DONE + result = TEST_SUCCESS; +out: + close(fuse_dev); + close(content_fd); + close(src_fd); + umount(mount_dir); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +/* + * State: + * Original: dst/folder1/content.txt + * ^ + * | + * | + * Backing: src/folder1/content.txt + * + * Step 1: open(folder1) - lookup folder1 with entry_timeout set to 0 + * Step 2: open(folder1) - lookup folder1 again to trigger revalidate wich will + * set backing fd + * + * Check 1: cat(content.txt) - check not receiving call on the fuse daemon + * and content is the same + */ +static int bpf_test_revalidate_handle_backing_fd(const char *mount_dir) +{ + const char *folder1 = "folder1"; + const char *content_file = "content.txt"; + const char *content = "hello world"; + int result = TEST_FAILURE; + int fuse_dev = -1; + int src_fd = -1; + int content_fd = -1; + int pid = -1; + int status; + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777)); + TEST(content_fd = s_creat(s_pathn(3, s(ft_src), s(folder1), s(content_file)), 0777), + content_fd != -1); + TESTEQUAL(write(content_fd, content, strlen(content)), strlen(content)); + TESTSYSCALL(close(content_fd)); + content_fd = -1; + TESTEQUAL(mount_fuse_no_init(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + int dst_folder1_fd = -1; + int dst_content_fd = -1; + int dst_content_read_size = -1; + char content_buffer[11] = {0}; + // Step 1: Lookup folder1 + TESTERR(dst_folder1_fd = s_open(s_path(s(mount_dir), s(folder1)), + O_RDONLY | O_CLOEXEC), dst_folder1_fd != -1); + TESTSYSCALL(close(dst_folder1_fd)); + dst_folder1_fd = -1; + // Step 2: Lookup folder1 again + TESTERR(dst_folder1_fd = s_open(s_path(s(mount_dir), s(folder1)), + O_RDONLY | O_CLOEXEC), dst_folder1_fd != -1); + TESTSYSCALL(close(dst_folder1_fd)); + dst_folder1_fd = -1; + // Check 1: Read content file (must be backed) + TESTERR(dst_content_fd = + s_open(s_pathn(3, s(mount_dir), s(folder1), s(content_file)), + O_RDONLY | O_CLOEXEC), dst_content_fd != -1); + TEST(dst_content_read_size = + read(dst_content_fd, content_buffer, strlen(content)), + dst_content_read_size == strlen(content) && + strcmp(content, content_buffer) == 0); + TESTSYSCALL(close(dst_content_fd)); + dst_content_fd = -1; + FUSE_DAEMON + struct fuse_attr attr = {}; + int backing_fd = -1; + TESTFUSEINITFLAGS(FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO); + // Step 1: Lookup folder1 set entry_timeout to 0 to trigger + // revalidate later + TESTFUSELOOKUP(folder1, 0); + TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr)); + TEST(backing_fd = s_open(s_path(s(ft_src), s(folder1)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd != -1); + TESTFUSEOUT2_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = attr.ino, + .generation = 0, + .entry_valid = 0, + .attr_valid = UINT64_MAX, + .entry_valid_nsec = 0, + .attr_valid_nsec = UINT32_MAX, + .attr = attr, + }), fuse_bpf_entry_out, ((struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + })); + TESTSYSCALL(close(backing_fd)); + // Step 1: Lookup folder1 as a reaction to revalidate call + // This attempts to change the backing node, which is not allowed on revalidate + TESTFUSELOOKUP(folder1, 0); + TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr)); + TEST(backing_fd = s_open(s_path(s(ft_src), s(folder1)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd != -1); + TESTFUSEOUT2_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = attr.ino, + .generation = 0, + .entry_valid = UINT64_MAX, + .attr_valid = UINT64_MAX, + .entry_valid_nsec = UINT32_MAX, + .attr_valid_nsec = UINT32_MAX, + .attr = attr, + }), fuse_bpf_entry_out, ((struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + })); + TESTSYSCALL(close(backing_fd)); + + // Lookup folder1 as a reaction to failed revalidate + TESTFUSELOOKUP(folder1, 0); + TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr)); + TEST(backing_fd = s_open(s_path(s(ft_src), s(folder1)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd != -1); + TESTFUSEOUT2_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = attr.ino, + .generation = 0, + .entry_valid = UINT64_MAX, + .attr_valid = UINT64_MAX, + .entry_valid_nsec = UINT32_MAX, + .attr_valid_nsec = UINT32_MAX, + .attr = attr, + }), fuse_bpf_entry_out, ((struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + })); + TESTSYSCALL(close(backing_fd)); + FUSE_DONE + result = TEST_SUCCESS; +out: + close(fuse_dev); + close(content_fd); + close(src_fd); + umount(mount_dir); + return result; +} + +static int bpf_test_lookup_postfilter(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *file1_name = "file1"; + const char *file2_name = "file2"; + const char *file3_name = "file3"; + int result = TEST_FAILURE; + int bpf_fd = -1; + int src_fd = -1; + int fuse_dev = -1; + int file_fd = -1; + int pid = -1; + int status; + + TEST(file_fd = s_creat(s_path(s(ft_src), s(file1_name)), 0777), + file_fd != -1); + TESTSYSCALL(close(file_fd)); + TEST(file_fd = s_creat(s_path(s(ft_src), s(file2_name)), 0777), + file_fd != -1); + TESTSYSCALL(close(file_fd)); + file_fd = -1; + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.lookup_postfilter_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "lookup_post", src_fd, &fuse_dev), 0); + FUSE_ACTION + int fd = -1; + + TESTEQUAL(s_open(s_path(s(mount_dir), s(file1_name)), O_RDONLY), + -1); + TESTEQUAL(errno, ENOENT); + TEST(fd = s_open(s_path(s(mount_dir), s(file2_name)), O_RDONLY), + fd != -1); + TESTSYSCALL(close(fd)); + TESTEQUAL(s_open(s_path(s(mount_dir), s(file3_name)), O_RDONLY), + -1); + FUSE_DAEMON + struct fuse_entry_out *feo; + uint32_t *err_in; + + TESTFUSELOOKUP(file1_name, FUSE_POSTFILTER); + TESTFUSEOUTERROR(-ENOENT); + + TESTFUSELOOKUP(file2_name, FUSE_POSTFILTER); + feo = (struct fuse_entry_out *) (bytes_in + + sizeof(struct fuse_in_header) + strlen(file2_name) + 1); + TESTFUSEOUT1(fuse_entry_out, *feo); + + TESTFUSELOOKUP_POST_ERRIN(file3_name, err_in); + TESTEQUAL(*err_in, -ENOENT); + TESTFUSEOUTERROR(-ENOENT); + FUSE_DONE + + result = TEST_SUCCESS; +out: + close(file_fd); + close(fuse_dev); + umount(mount_dir); + close(src_fd); + close(bpf_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static void parse_range(const char *ranges, bool *run_test, size_t tests) +{ + size_t i; + char *range; + + for (i = 0; i < tests; ++i) + run_test[i] = false; + + range = strtok(optarg, ","); + while (range) { + char *dash = strchr(range, '-'); + + if (dash) { + size_t start = 1, end = tests; + char *end_ptr; + + if (dash > range) { + start = strtol(range, &end_ptr, 10); + if (*end_ptr != '-' || start <= 0 || start > tests) + ksft_exit_fail_msg("Bad range\n"); + } + + if (dash[1]) { + end = strtol(dash + 1, &end_ptr, 10); + if (*end_ptr || end <= start || end > tests) + ksft_exit_fail_msg("Bad range\n"); + } + + for (i = start; i <= end; ++i) + run_test[i - 1] = true; + } else { + char *end; + long value = strtol(range, &end, 10); + + if (*end || value <= 0 || value > tests) + ksft_exit_fail_msg("Bad range\n"); + run_test[value - 1] = true; + } + range = strtok(NULL, ","); + } +} + +static int parse_options(int argc, char *const *argv, bool *run_test, + size_t tests) +{ + signed char c; + + while ((c = getopt(argc, argv, "f:t:v")) != -1) + switch (c) { + case 'f': + test_options.file = strtol(optarg, NULL, 10); + break; + + case 't': + parse_range(optarg, run_test, tests); + break; + + case 'v': + test_options.verbose = true; + break; + + default: + return -EINVAL; + } + + return 0; +} + +struct test_case { + int (*pfunc)(const char *dir); + const char *name; +}; + +static void run_one_test(const char *mount_dir, + const struct test_case *test_case) +{ + ksft_print_msg("Running %s\n", test_case->name); + bpf_clear_trace(); + if (test_case->pfunc(mount_dir) == TEST_SUCCESS) + ksft_test_result_pass("%s\n", test_case->name); + else + ksft_test_result_fail("%s\n", test_case->name); +} + +int main(int argc, char *argv[]) +{ + char *mount_dir = NULL; + char *src_dir = NULL; + int i; + int fd, count; + +#define MAKE_TEST(test) \ + { \ + test, #test \ + } + const struct test_case cases[] = { + MAKE_TEST(basic_test), + MAKE_TEST(bpf_test_real), + MAKE_TEST(bpf_test_partial), + MAKE_TEST(bpf_test_attrs), + MAKE_TEST(bpf_test_readdir), + MAKE_TEST(bpf_test_creat), + MAKE_TEST(bpf_test_hidden_entries), + MAKE_TEST(bpf_test_dir), + MAKE_TEST(bpf_test_file_early_close), + MAKE_TEST(bpf_test_file_late_close), + MAKE_TEST(bpf_test_mknod), + MAKE_TEST(bpf_test_largedir), + MAKE_TEST(bpf_test_link), + MAKE_TEST(bpf_test_symlink), + MAKE_TEST(bpf_test_xattr), + MAKE_TEST(bpf_test_redact_readdir), + MAKE_TEST(bpf_test_set_backing), + MAKE_TEST(bpf_test_set_backing_no_ioctl), + MAKE_TEST(bpf_test_set_backing_folder), + MAKE_TEST(bpf_test_remove_backing), + MAKE_TEST(bpf_test_dir_rename), + MAKE_TEST(bpf_test_file_rename), + MAKE_TEST(bpf_test_alter_errcode_bpf), + MAKE_TEST(bpf_test_alter_errcode_userspace), + MAKE_TEST(mmap_test), + MAKE_TEST(readdir_perms_test), + MAKE_TEST(bpf_test_statfs), + MAKE_TEST(bpf_test_lseek), + MAKE_TEST(bpf_test_readdirplus_not_overriding_backing), + MAKE_TEST(bpf_test_no_readdirplus_without_nodeid), + MAKE_TEST(bpf_test_revalidate_handle_backing_fd), + MAKE_TEST(bpf_test_lookup_postfilter), + MAKE_TEST(flock_test), + //MAKE_TEST(bpf_test_verifier), + //MAKE_TEST(bpf_test_verifier_out_args), + //MAKE_TEST(bpf_test_verifier_packet_invalidation), + //MAKE_TEST(bpf_test_verifier_nonsense_read) + }; +#undef MAKE_TEST + + bool run_test[ARRAY_SIZE(cases)]; + + for (int i = 0; i < ARRAY_SIZE(cases); ++i) + run_test[i] = true; + + if (parse_options(argc, argv, run_test, ARRAY_SIZE(cases))) + ksft_exit_fail_msg("Bad options\n"); + + // Seed randomness pool for testing on QEMU + // NOTE - this abuses the concept of randomness - do *not* ever do this + // on a machine for production use - the device will think it has good + // randomness when it does not. + fd = open("/dev/urandom", O_WRONLY | O_CLOEXEC); + count = 4096; + for (int i = 0; i < 128; ++i) + ioctl(fd, RNDADDTOENTCNT, &count); + close(fd); + + ksft_print_header(); + + if (geteuid() != 0) + ksft_print_msg("Not a root, might fail to mount.\n"); + + if (tracing_on() != TEST_SUCCESS) + ksft_exit_fail_msg("Can't turn on tracing\n"); + + src_dir = setup_mount_dir(ft_src); + mount_dir = setup_mount_dir(ft_dst); + if (src_dir == NULL || mount_dir == NULL) + ksft_exit_fail_msg("Can't create a mount dir\n"); + + ksft_set_plan(ARRAY_SIZE(run_test)); + + for (i = 0; i < ARRAY_SIZE(run_test); ++i) + if (run_test[i]) { + delete_dir_tree(mount_dir, false); + delete_dir_tree(src_dir, false); + run_one_test(mount_dir, &cases[i]); + } else + ksft_cnt.ksft_xskip++; + + umount2(mount_dir, MNT_FORCE); + delete_dir_tree(mount_dir, true); + delete_dir_tree(src_dir, true); + return !ksft_get_fail_cnt() ? ksft_exit_pass() : ksft_exit_fail(); +} diff --git a/tools/testing/selftests/filesystems/fuse/test.bpf.c b/tools/testing/selftests/filesystems/fuse/test.bpf.c new file mode 100644 index 000000000000..1d31948264f4 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/test.bpf.c @@ -0,0 +1,1045 @@ +// 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") = "GPL"; + +#if 0 +inline __always_inline int local_strcmp(const char *a, const char *b) +{ + int i; + + for (i = 0; i < __builtin_strlen(b) + 1; ++i) + if (a[i] != b[i]) + return -1; + return 0; +} + + +/* This is a macro to enforce inlining. Without it, the compiler will do the wrong thing for bpf */ +#define strcmp_check(a, b, end_b) \ + (((b) + __builtin_strlen(a) + 1 > (end_b)) ? -1 : local_strcmp((b), (a))) +#endif + +//trace ops + +BPF_STRUCT_OPS(uint32_t, trace_access_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_access_in *in) +{ + bpf_printk("Access: %d", meta->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_getattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_getattr_in *in) +{ + bpf_printk("Get Attr %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_setattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_setattr_in *in) +{ + bpf_printk("Set Attr %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_opendir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_open_in *in) +{ + bpf_printk("Open Dir: %d", meta->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_readdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + bpf_printk("Read Dir: fh: %lu", in->fh, in->offset); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, user_lookup_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char *name_buf; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 1); + bpf_printk("Lookup: %lx %s", meta->nodeid, name_buf); + if (meta->nodeid == 1) + return BPF_FUSE_USER_PREFILTER; + else + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_lookup_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char *name_buf; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 1); + bpf_printk("Lookup: %lx %s", meta->nodeid, name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_mknod_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_mknod_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("mknod %s %x %x", name_buf, in->rdev | in->mode, in->umask); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_mkdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_mkdir_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("mkdir: %s %x %x", name_buf, in->mode, in->umask); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_rmdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("rmdir: %s", name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_rename_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_rename_in *in, struct fuse_buffer *old_name, + struct fuse_buffer *new_name) +{ + struct bpf_dynptr old_name_ptr; + struct bpf_dynptr new_name_ptr; + char old_name_buf[255]; + //char new_name_buf[255]; + + bpf_fuse_get_ro_dynptr(old_name, &old_name_ptr); + //bpf_fuse_get_ro_dynptr(new_name, &new_name_ptr); + bpf_dynptr_read(old_name_buf, 255, &old_name_ptr, 0, 0); + //bpf_dynptr_read(new_name_buf, 255, &new_name_ptr, 0, 0); + bpf_printk("rename from %s", old_name_buf); + //bpf_printk("rename to %s", new_name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_rename2_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_rename2_in *in, struct fuse_buffer *old_name, + struct fuse_buffer *new_name) +{ + struct bpf_dynptr old_name_ptr; + //struct bpf_dynptr new_name_ptr; + char old_name_buf[255]; + //char new_name_buf[255]; + + bpf_fuse_get_ro_dynptr(old_name, &old_name_ptr); + //bpf_fuse_get_ro_dynptr(new_name, &new_name_ptr); + bpf_dynptr_read(old_name_buf, 255, &old_name_ptr, 0, 0); + //bpf_dynptr_read(new_name_buf, 255, &new_name_ptr, 0, 0); + bpf_printk("rename(%x) from %s", in->flags, old_name_buf); + //bpf_printk("rename to %s", new_name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_unlink_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("unlink: %s", name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_link_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_link_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char dst_name[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(dst_name, 255, &name_ptr, 0, 0); + bpf_printk("link: %d %s", in->oldnodeid, dst_name); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_symlink_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name, struct fuse_buffer *path) +{ + struct bpf_dynptr name_ptr; + //struct bpf_dynptr path_ptr; + char link_name[255]; + //char link_path[4096]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + //bpf_fuse_get_ro_dynptr(path, &path_ptr); + bpf_dynptr_read(link_name, 255, &name_ptr, 0, 0); + //bpf_dynptr_read(link_path, 4096, &path_ptr, 0, 0); + + bpf_printk("symlink from %s", link_name); + //bpf_printk("symlink to %s", link_path); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_get_link_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char link_name[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(link_name, 255, &name_ptr, 0, 0); + bpf_printk("readlink from %s", link_name); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_release_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_release_in *in) +{ + bpf_printk("Release: %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_releasedir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_release_in *in) +{ + bpf_printk("Release Dir: %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_create_open_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_create_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("Create %s", name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_open_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_open_in *in) +{ + bpf_printk("Open: %d", meta->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_read_iter_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + bpf_printk("Read: fh: %lu, offset %lu, size %lu", + in->fh, in->offset, in->size); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_write_iter_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_write_in *in) +{ + bpf_printk("Write: fh: %lu, offset %lu, size %lu", + in->fh, in->offset, in->size); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_flush_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_flush_in *in) +{ + bpf_printk("flush %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_file_fallocate_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_fallocate_in *in) +{ + bpf_printk("fallocate %d %lu", in->fh, in->length); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_getxattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_getxattr_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("getxattr %d %s", meta->nodeid, name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_listxattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_getxattr_in *in) +{ + bpf_printk("listxattr %d %d", meta->nodeid, in->size); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_setxattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_setxattr_in *in, struct fuse_buffer *name, + struct fuse_buffer *value) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("setxattr %d %s", meta->nodeid, name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_removexattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("removexattr %d %s", meta->nodeid, name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_statfs_prefilter, const struct bpf_fuse_meta_info *meta) +{ + bpf_printk("statfs %d", meta->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_lseek_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_lseek_in *in) +{ + bpf_printk("lseek type:%d, offset:%lld", in->whence, in->offset); + return BPF_FUSE_CONTINUE; +} + +// readdir_test_ops +BPF_STRUCT_OPS(uint32_t, readdir_redact_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + bpf_printk("readdir %d", in->fh); + return BPF_FUSE_POSTFILTER; +} + +BPF_STRUCT_OPS(uint32_t, readdir_redact_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_read_in *in, + struct fuse_read_out *out, struct fuse_buffer *buffer) +{ + bpf_printk("readdir postfilter %x", in->fh); + return BPF_FUSE_USER_POSTFILTER; +} + +// test operations + +BPF_STRUCT_OPS(uint32_t, test_lookup_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char *name_buf; + bool backing = false; + int ret; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + + /* bpf_dynptr_slice will only return a pointer if the dynptr is long enough */ + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 8); + if (name_buf) { + if (bpf_strncmp(name_buf, 8, "partial") == 0) + backing = true; + goto print; + } + + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 6); + if (name_buf) { + if (bpf_strncmp(name_buf, 6, "file1") == 0) + backing = true; + if (bpf_strncmp(name_buf, 6, "file2") == 0) + backing = true; + goto print; + } + + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 5); + if (name_buf) { + if (bpf_strncmp(name_buf, 5, "dir2") == 0) + backing = true; + if (bpf_strncmp(name_buf, 5, "real") == 0) + backing = true; + goto print; + } + + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 4); + if (name_buf) { + if (bpf_strncmp(name_buf, 4, "dir") == 0) + backing = true; + goto print; + } +print: + if (name_buf) + bpf_printk("lookup %s %d", name_buf, backing); + else + bpf_printk("lookup [name length under 3] %d", backing); + return backing ? BPF_FUSE_POSTFILTER : BPF_FUSE_USER; +} + +BPF_STRUCT_OPS(uint32_t, test_lookup_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name, + struct fuse_entry_out *out, struct fuse_buffer *entries) +{ + struct bpf_dynptr name_ptr; + char *name_buf; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 8); + if (name_buf) { + if (bpf_strncmp(name_buf, 8, "partial") == 0) + out->nodeid = 6; + goto print; + } + + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 5); + if (name_buf) { + if (bpf_strncmp(name_buf, 5, "real") == 0) + out->nodeid = 5; + goto print; + } +print: + if (name_buf) + bpf_printk("post-lookup %s %d", name_buf, out->nodeid); + else + bpf_printk("post-lookup [name length under 4] %d", out->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, test_open_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_open_in *in) +{ + int backing = BPF_FUSE_USER; + + switch (meta->nodeid) { + case 5: + backing = BPF_FUSE_CONTINUE; + bpf_printk("Setting BPF_FUSE_CONTINUE:%d", BPF_FUSE_CONTINUE); + break; + + case 6: + backing = BPF_FUSE_POSTFILTER; + bpf_printk("Setting BPF_FUSE_CONTINUE:%d", BPF_FUSE_POSTFILTER); + break; + + default: + bpf_printk("Setting NOTHING %d", BPF_FUSE_USER); + break; + } + + bpf_printk("open: %d %d", meta->nodeid, backing); + return backing; +} + +BPF_STRUCT_OPS(uint32_t, test_open_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_open_in *in, + struct fuse_open_out *out) +{ + bpf_printk("open postfilter"); + return BPF_FUSE_USER_POSTFILTER; +} + +BPF_STRUCT_OPS(uint32_t, test_read_iter_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + bpf_printk("read %llu %llu", in->fh, in->offset); + if (in->fh == 1 && in->offset == 0) + return BPF_FUSE_USER; + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, test_getattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_getattr_in *in) +{ + /* real and partial use backing file */ + int backing = BPF_FUSE_USER; + + switch (meta->nodeid) { + case 1: + case 5: + case 6: + /* + * TODO: Find better solution + * Add 100 to stop clang compiling to jump table which bpf hates + */ + case 100: + backing = BPF_FUSE_CONTINUE; + break; + } + + bpf_printk("getattr %d %d", meta->nodeid, backing); + return backing; +} + +BPF_STRUCT_OPS(uint32_t, test_setattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_setattr_in *in) +{ + /* real and partial use backing file */ + int backing = BPF_FUSE_USER; + + switch (meta->nodeid) { + case 1: + case 5: + case 6: + /* TODO See above */ + case 100: + backing = BPF_FUSE_CONTINUE; + break; + } + + bpf_printk("setattr %d %d", meta->nodeid, backing); + return backing; +} + +BPF_STRUCT_OPS(uint32_t, test_opendir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_open_in *in) +{ + int backing = BPF_FUSE_USER; + + switch (meta->nodeid) { + case 1: + backing = BPF_FUSE_POSTFILTER; + break; + } + bpf_printk("opendir %d %d", meta->nodeid, backing); + return backing; +} + +BPF_STRUCT_OPS(uint32_t, test_opendir_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_open_in *in, + struct fuse_open_out *out) +{ + out->fh = 2; + bpf_printk("opendir postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, test_readdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + int backing = BPF_FUSE_USER; + + if (in->fh == 2) + backing = BPF_FUSE_POSTFILTER; + + bpf_printk("readdir %d %d", in->fh, backing); + return backing; +} + +BPF_STRUCT_OPS(uint32_t, test_readdir_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_read_in *in, + struct fuse_read_out *out, struct fuse_buffer *buffer) +{ + int backing = BPF_FUSE_CONTINUE; + + if (in->fh == 2) + backing = BPF_FUSE_USER_POSTFILTER; + + bpf_printk("readdir postfilter %d %d", in->fh, backing); + return backing; +} + +// test_hidden + +BPF_STRUCT_OPS(uint32_t, hidden_lookup_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char *name_buf; + bool backing = false; + int ret; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + + /* bpf_dynptr_slice will only return a pointer if the dynptr is long enough */ + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 5); + if (name_buf) + bpf_printk("Lookup: %s", name_buf); + else + bpf_printk("lookup [name length under 4]"); + if (name_buf) { + if (bpf_strncmp(name_buf, 5, "show") == 0) + return BPF_FUSE_CONTINUE; + if (bpf_strncmp(name_buf, 5, "hide") == 0) + return -ENOENT; + } + + return BPF_FUSE_CONTINUE; +} + +// test_error + +BPF_STRUCT_OPS(uint32_t, error_mkdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_mkdir_in *in, struct fuse_buffer *name) +{ + bpf_printk("mkdir"); + + return BPF_FUSE_POSTFILTER; +} + +BPF_STRUCT_OPS(uint32_t, error_mkdir_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_mkdir_in *in, const struct fuse_buffer *name) +{ + bpf_printk("mkdir postfilter"); + + if (meta->error_in == -EEXIST) + return -EPERM; + return 0; +} + +BPF_STRUCT_OPS(uint32_t, error_lookup_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char *name_buf; + bool backing = false; + int ret; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + + /* bpf_dynptr_slice will only return a pointer if the dynptr is long enough */ + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 1); + bpf_printk("lookup prefilter %s", name); + return BPF_FUSE_POSTFILTER; +} + +BPF_STRUCT_OPS(uint32_t, error_lookup_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name, + struct fuse_entry_out *out, struct fuse_buffer *entries) +{ + struct bpf_dynptr name_ptr; + char *name_buf; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 13); + if (name_buf) + bpf_printk("post-lookup %s %d", name_buf, out->nodeid); + else + bpf_printk("post-lookup [name length under 13] %d", out->nodeid); + if (name_buf) { + if (bpf_strncmp(name_buf, 13, "doesnotexist") == 0) { + bpf_printk("lookup postfilter doesnotexist"); + return BPF_FUSE_USER_POSTFILTER; + } + } + + return 0; +} + +// test readdirplus + +BPF_STRUCT_OPS(uint32_t, readdirplus_readdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + return BPF_FUSE_USER; +} + +// Test passthrough + +// Reuse error_lookup_prefilter + +BPF_STRUCT_OPS(uint32_t, passthrough_lookup_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name, + struct fuse_entry_out *out, struct fuse_buffer *entries) +{ + struct bpf_dynptr name_ptr; + struct bpf_dynptr entries_ptr; + char *name_buf; + struct fuse_bpf_entry_out entry; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 1); + if (name_buf) + bpf_printk("post-lookup %s %d", name_buf, out->nodeid); + else + bpf_printk("post-lookup [name length under 1???] %d", out->nodeid); + bpf_fuse_get_rw_dynptr(entries, &entries_ptr, sizeof(entry), false); + entry = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_REMOVE_BPF, + }; + bpf_dynptr_write(&entries_ptr, 0, &entry, sizeof(entry), 0); + + return BPF_FUSE_USER_POSTFILTER; +} + +// lookup_postfilter_ops + +//reuse error_lookup_prefilter + +BPF_STRUCT_OPS(uint32_t, test_bpf_lookup_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name, + struct fuse_entry_out *out, struct fuse_buffer *entries) +{ + return BPF_FUSE_USER_POSTFILTER; +} + +SEC(".struct_ops") +struct fuse_ops trace_ops = { + .open_prefilter = (void *)trace_open_prefilter, + .opendir_prefilter = (void *)trace_opendir_prefilter, + .create_open_prefilter = (void *)trace_create_open_prefilter, + .release_prefilter = (void *)trace_release_prefilter, + .releasedir_prefilter = (void *)trace_releasedir_prefilter, + .flush_prefilter = (void *)trace_flush_prefilter, + .lseek_prefilter = (void *)trace_lseek_prefilter, + //.copy_file_range_prefilter = (void *)trace_copy_file_range_prefilter, + //.fsync_prefilter = (void *)trace_fsync_prefilter, + //.dir_fsync_prefilter = (void *)trace_dir_fsync_prefilter, + .getxattr_prefilter = (void *)trace_getxattr_prefilter, + .listxattr_prefilter = (void *)trace_listxattr_prefilter, + .setxattr_prefilter = (void *)trace_setxattr_prefilter, + .removexattr_prefilter = (void *)trace_removexattr_prefilter, + .read_iter_prefilter = (void *)trace_read_iter_prefilter, + .write_iter_prefilter = (void *)trace_write_iter_prefilter, + .file_fallocate_prefilter = (void *)trace_file_fallocate_prefilter, + .lookup_prefilter = (void *)trace_lookup_prefilter, + .mknod_prefilter = (void *)trace_mknod_prefilter, + .mkdir_prefilter = (void *)trace_mkdir_prefilter, + .rmdir_prefilter = (void *)trace_rmdir_prefilter, + .rename2_prefilter = (void *)trace_rename2_prefilter, + .rename_prefilter = (void *)trace_rename_prefilter, + .unlink_prefilter = (void *)trace_unlink_prefilter, + .link_prefilter = (void *)trace_link_prefilter, + .getattr_prefilter = (void *)trace_getattr_prefilter, + .setattr_prefilter = (void *)trace_setattr_prefilter, + .statfs_prefilter = (void *)trace_statfs_prefilter, + .get_link_prefilter = (void *)trace_get_link_prefilter, + .symlink_prefilter = (void *)trace_symlink_prefilter, + .readdir_prefilter = (void *)trace_readdir_prefilter, + .access_prefilter = (void *)trace_access_prefilter, + .name = "trace_ops", +}; + +SEC(".struct_ops") +struct fuse_ops legacy_ops = { + .open_prefilter = (void *)trace_open_prefilter, + .opendir_prefilter = (void *)trace_opendir_prefilter, + .create_open_prefilter = (void *)trace_create_open_prefilter, + .release_prefilter = (void *)trace_release_prefilter, + .releasedir_prefilter = (void *)trace_releasedir_prefilter, + .flush_prefilter = (void *)trace_flush_prefilter, + .lseek_prefilter = (void *)trace_lseek_prefilter, + //.copy_file_range_prefilter = (void *)trace_copy_file_range_prefilter, + //.fsync_prefilter = (void *)trace_fsync_prefilter, + //.dir_fsync_prefilter = (void *)trace_dir_fsync_prefilter, + .getxattr_prefilter = (void *)trace_getxattr_prefilter, + .listxattr_prefilter = (void *)trace_listxattr_prefilter, + .setxattr_prefilter = (void *)trace_setxattr_prefilter, + .removexattr_prefilter = (void *)trace_removexattr_prefilter, + .read_iter_prefilter = (void *)trace_read_iter_prefilter, + .write_iter_prefilter = (void *)trace_write_iter_prefilter, + .file_fallocate_prefilter = (void *)trace_file_fallocate_prefilter, + .lookup_prefilter = (void *)user_lookup_prefilter, + .mknod_prefilter = (void *)trace_mknod_prefilter, + .mkdir_prefilter = (void *)trace_mkdir_prefilter, + .rmdir_prefilter = (void *)trace_rmdir_prefilter, + .rename2_prefilter = (void *)trace_rename2_prefilter, + .rename_prefilter = (void *)trace_rename_prefilter, + .unlink_prefilter = (void *)trace_unlink_prefilter, + .link_prefilter = (void *)trace_link_prefilter, + .getattr_prefilter = (void *)trace_getattr_prefilter, + .setattr_prefilter = (void *)trace_setattr_prefilter, + .statfs_prefilter = (void *)trace_statfs_prefilter, + .get_link_prefilter = (void *)trace_get_link_prefilter, + .symlink_prefilter = (void *)trace_symlink_prefilter, + .readdir_prefilter = (void *)trace_readdir_prefilter, + .access_prefilter = (void *)trace_access_prefilter, + .name = "legacy_ops", +}; + +SEC(".struct_ops") +struct fuse_ops test_trace_ops = { + .open_prefilter = (void *)test_open_prefilter, + .open_postfilter = (void *)test_open_postfilter, + .opendir_prefilter = (void *)test_opendir_prefilter, + .opendir_postfilter = (void *)test_opendir_postfilter, + .create_open_prefilter = (void *)trace_create_open_prefilter, + .release_prefilter = (void *)trace_release_prefilter, + .releasedir_prefilter = (void *)trace_releasedir_prefilter, + .flush_prefilter = (void *)trace_flush_prefilter, + .lseek_prefilter = (void *)trace_lseek_prefilter, + //.copy_file_range_prefilter = (void *)trace_copy_file_range_prefilter, + //.fsync_prefilter = (void *)trace_fsync_prefilter, + //.dir_fsync_prefilter = (void *)trace_dir_fsync_prefilter, + .getxattr_prefilter = (void *)trace_getxattr_prefilter, + .listxattr_prefilter = (void *)trace_listxattr_prefilter, + .setxattr_prefilter = (void *)trace_setxattr_prefilter, + .removexattr_prefilter = (void *)trace_removexattr_prefilter, + .read_iter_prefilter = (void *)test_read_iter_prefilter, + .write_iter_prefilter = (void *)trace_write_iter_prefilter, + .file_fallocate_prefilter = (void *)trace_file_fallocate_prefilter, + .lookup_prefilter = (void *)test_lookup_prefilter, + .lookup_postfilter = (void *)test_lookup_postfilter, + .mknod_prefilter = (void *)trace_mknod_prefilter, + .mkdir_prefilter = (void *)trace_mkdir_prefilter, + .rmdir_prefilter = (void *)trace_rmdir_prefilter, + .rename2_prefilter = (void *)trace_rename2_prefilter, + .rename_prefilter = (void *)trace_rename_prefilter, + .unlink_prefilter = (void *)trace_unlink_prefilter, + .link_prefilter = (void *)trace_link_prefilter, + .getattr_prefilter = (void *)test_getattr_prefilter, + .setattr_prefilter = (void *)test_setattr_prefilter, + .statfs_prefilter = (void *)trace_statfs_prefilter, + .get_link_prefilter = (void *)trace_get_link_prefilter, + .symlink_prefilter = (void *)trace_symlink_prefilter, + .readdir_prefilter = (void *)test_readdir_prefilter, + .readdir_postfilter = (void *)test_readdir_postfilter, + .access_prefilter = (void *)trace_access_prefilter, + .name = "test_trace_ops", +}; + +SEC(".struct_ops") +struct fuse_ops readdir_redact_ops = { + .readdir_prefilter = (void *)readdir_redact_prefilter, + .readdir_postfilter = (void *)readdir_redact_postfilter, + .name = "readdir_redact", +}; + +SEC(".struct_ops") +struct fuse_ops test_hidden_ops = { + .lookup_prefilter = (void *)hidden_lookup_prefilter, + .access_prefilter = (void *)trace_access_prefilter, + .create_open_prefilter = (void *)trace_create_open_prefilter, + .name = "test_hidden", +}; + +SEC(".struct_ops") +struct fuse_ops test_error_ops = { + .lookup_prefilter = (void *)error_lookup_prefilter, + .lookup_postfilter = (void *)error_lookup_postfilter, + .mkdir_prefilter = (void *)error_mkdir_prefilter, + .mkdir_postfilter = (void *)error_mkdir_postfilter, + .name = "test_error", +}; + +SEC(".struct_ops") +struct fuse_ops readdir_plus_ops = { + .readdir_prefilter = (void *)readdirplus_readdir_prefilter, + .name = "readdir_plus", +}; + +SEC(".struct_ops") +struct fuse_ops passthrough_ops = { + .lookup_prefilter = (void *)error_lookup_prefilter, + .lookup_postfilter = (void *)passthrough_lookup_postfilter, + .name = "passthrough", +}; + +SEC(".struct_ops") +struct fuse_ops lookup_postfilter_ops = { + .lookup_prefilter = (void *)error_lookup_prefilter, + .lookup_postfilter = (void *)test_bpf_lookup_postfilter, + .name = "lookup_post", +}; + +#if 0 +//TODO: Figure out what to do with these +SEC("test_verify") + +int verify_test(struct __bpf_fuse_args *fa) +{ + if (fa->opcode == (FUSE_MKDIR | FUSE_PREFILTER)) { + const char *start; + const char *end; + const struct fuse_mkdir_in *in; + + start = (void *)(long) fa->in_args[0].value; + end = (void *)(long) fa->in_args[0].end_offset; + if (start + sizeof(*in) <= end) { + in = (struct fuse_mkdir_in *)(start); + bpf_printk("test1: %d %d", in->mode, in->umask); + } + + return BPF_FUSE_CONTINUE; + } + return BPF_FUSE_CONTINUE; +} + +SEC("test_verify_fail") + +int verify_fail_test(struct __bpf_fuse_args *fa) +{ + struct t { + uint32_t a; + uint32_t b; + char d[]; + }; + if (fa->opcode == (FUSE_MKDIR | FUSE_PREFILTER)) { + const char *start; + const char *end; + const struct t *c; + + start = (void *)(long) fa->in_args[0].value; + end = (void *)(long) fa->in_args[0].end_offset; + if (start + sizeof(struct t) <= end) { + c = (struct t *)start; + bpf_printk("test1: %d %d %d", c->a, c->b, c->d[0]); + } + return BPF_FUSE_CONTINUE; + } + return BPF_FUSE_CONTINUE; +} + +SEC("test_verify_fail2") + +int verify_fail_test2(struct __bpf_fuse_args *fa) +{ + if (fa->opcode == (FUSE_MKDIR | FUSE_PREFILTER)) { + const char *start; + const char *end; + struct fuse_mkdir_in *c; + + start = (void *)(long) fa->in_args[0].value; + end = (void *)(long) fa->in_args[1].end_offset; + if (start + sizeof(*c) <= end) { + c = (struct fuse_mkdir_in *)start; + bpf_printk("test1: %d %d", c->mode, c->umask); + } + return BPF_FUSE_CONTINUE; + } + return BPF_FUSE_CONTINUE; +} + +SEC("test_verify_fail3") +/* Cannot write directly to fa */ +int verify_fail_test3(struct __bpf_fuse_args *fa) +{ + if (fa->opcode == (FUSE_LOOKUP | FUSE_POSTFILTER)) { + const char *name = (void *)(long)fa->in_args[0].value; + const char *end = (void *)(long)fa->in_args[0].end_offset; + struct fuse_entry_out *feo = fa_verify_out(fa, 0, sizeof(*feo)); + + if (!feo) + return -1; + + if (strcmp_check("real", name, end) == 0) + feo->nodeid = 5; + else if (strcmp_check("partial", name, end) == 0) + feo->nodeid = 6; + + bpf_printk("post-lookup %s %d", name, feo->nodeid); + return BPF_FUSE_CONTINUE; + } + return BPF_FUSE_CONTINUE; +} + +SEC("test_verify_fail4") +/* Cannot write outside of requested area */ +int verify_fail_test4(struct __bpf_fuse_args *fa) +{ + if (fa->opcode == (FUSE_LOOKUP | FUSE_POSTFILTER)) { + const char *name = (void *)(long)fa->in_args[0].value; + const char *end = (void *)(long)fa->in_args[0].end_offset; + struct fuse_entry_out *feo = bpf_make_writable_out(fa, 0, fa->out_args[0].value, + 1, true); + + if (!feo) + return -1; + + if (strcmp_check("real", name, end) == 0) + feo->nodeid = 5; + else if (strcmp_check("partial", name, end) == 0) + feo->nodeid = 6; + + bpf_printk("post-lookup %s %d", name, feo->nodeid); + return BPF_FUSE_CONTINUE; + } + return BPF_FUSE_CONTINUE; +} + +SEC("test_verify_fail5") +/* Cannot use old verification after requesting writable */ +int verify_fail_test5(struct __bpf_fuse_args *fa) +{ + if (fa->opcode == (FUSE_LOOKUP | FUSE_POSTFILTER)) { + struct fuse_entry_out *feo; + struct fuse_entry_out *feo_w; + + feo = fa_verify_out(fa, 0, sizeof(*feo)); + if (!feo) + return -1; + + feo_w = bpf_make_writable_out(fa, 0, fa->out_args[0].value, sizeof(*feo_w), true); + bpf_printk("post-lookup %d", feo->nodeid); + if (!feo_w) + return -1; + + feo_w->nodeid = 5; + + return BPF_FUSE_CONTINUE; + } + return BPF_FUSE_CONTINUE; +} + +SEC("test_verify5") +/* Can use new verification after requesting writable */ +int verify_pass_test5(struct __bpf_fuse_args *fa) +{ + if (fa->opcode == (FUSE_LOOKUP | FUSE_POSTFILTER)) { + struct fuse_entry_out *feo; + struct fuse_entry_out *feo_w; + + feo = fa_verify_out(fa, 0, sizeof(*feo)); + if (!feo) + return -1; + + bpf_printk("post-lookup %d", feo->nodeid); + + feo_w = bpf_make_writable_out(fa, 0, fa->out_args[0].value, sizeof(*feo_w), true); + + feo = fa_verify_out(fa, 0, sizeof(*feo)); + if (feo) + bpf_printk("post-lookup %d", feo->nodeid); + if (!feo_w) + return -1; + + feo_w->nodeid = 5; + + return BPF_FUSE_CONTINUE; + } + return BPF_FUSE_CONTINUE; +} + +SEC("test_verify_fail6") +/* Reading context from a nonsense offset is not allowed */ +int verify_pass_test6(struct __bpf_fuse_args *fa) +{ + char *nonsense = (char *)fa; + + bpf_printk("post-lookup %d", nonsense[1]); + + return BPF_FUSE_CONTINUE; +} +#endif diff --git a/tools/testing/selftests/filesystems/fuse/test_framework.h b/tools/testing/selftests/filesystems/fuse/test_framework.h new file mode 100644 index 000000000000..24896b5e172f --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/test_framework.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Google LLC + */ + +#ifndef _TEST_FRAMEWORK_H +#define _TEST_FRAMEWORK_H + +#include +#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__ == __ORDER_LITTLE_ENDIAN__ +#define le16_to_cpu(x) (x) +#define le32_to_cpu(x) (x) +#define le64_to_cpu(x) (x) +#else +#error Big endian not supported! +#endif + +struct _test_options { + int file; + bool verbose; +}; + +extern struct _test_options test_options; + +#define TESTCOND(condition) \ + do { \ + if (!(condition)) { \ + ksft_print_msg("%s failed %d\n", \ + __func__, __LINE__); \ + goto out; \ + } else if (test_options.verbose) \ + ksft_print_msg("%s succeeded %d\n", \ + __func__, __LINE__); \ + } while (false) + +#define TESTCONDERR(condition) \ + do { \ + if (!(condition)) { \ + ksft_print_msg("%s failed %d\n", \ + __func__, __LINE__); \ + ksft_print_msg("Error %d (\"%s\")\n", \ + errno, strerror(errno)); \ + goto out; \ + } else if (test_options.verbose) \ + ksft_print_msg("%s succeeded %d\n", \ + __func__, __LINE__); \ + } while (false) + +#define TEST(statement, condition) \ + do { \ + statement; \ + TESTCOND(condition); \ + } while (false) + +#define TESTERR(statement, condition) \ + do { \ + statement; \ + TESTCONDERR(condition); \ + } while (false) + +enum _operator { + _eq, + _ne, + _ge, +}; + +static const char * const _operator_name[] = { + "==", + "!=", + ">=", +}; + +#define _TEST_OPERATOR(name, _type, format_specifier) \ +static inline int _test_operator_##name(const char *func, int line, \ + _type a, _type b, enum _operator o) \ +{ \ + bool pass; \ + switch (o) { \ + case _eq: pass = a == b; break; \ + case _ne: pass = a != b; break; \ + case _ge: pass = a >= b; break; \ + } \ + \ + if (!pass) \ + ksft_print_msg("Failed: %s at line %d, " \ + format_specifier " %s " \ + format_specifier "\n", \ + func, line, a, _operator_name[o], b); \ + else if (test_options.verbose) \ + ksft_print_msg("Passed: %s at line %d, " \ + format_specifier " %s " \ + format_specifier "\n", \ + func, line, a, _operator_name[o], b); \ + \ + return pass ? TEST_SUCCESS : TEST_FAILURE; \ +} + +_TEST_OPERATOR(i, int, "%d") +_TEST_OPERATOR(ui, unsigned int, "%u") +_TEST_OPERATOR(lui, unsigned long, "%lu") +_TEST_OPERATOR(ss, ssize_t, "%zd") +_TEST_OPERATOR(vp, void *, "%px") +_TEST_OPERATOR(cp, char *, "%px") + +#define _CALL_TO(_type, name, a, b, o) \ + _type:_test_operator_##name(__func__, __LINE__, \ + (_type) (long long) (a), \ + (_type) (long long) (b), o) + +#define TESTOPERATOR(a, b, o) \ + do { \ + if (_Generic((a), \ + _CALL_TO(int, i, a, b, o), \ + _CALL_TO(unsigned int, ui, a, b, o), \ + _CALL_TO(unsigned long, lui, a, b, o), \ + _CALL_TO(ssize_t, ss, a, b, o), \ + _CALL_TO(void *, vp, a, b, o), \ + _CALL_TO(char *, cp, a, b, o) \ + )) \ + goto out; \ + } while (false) + +#define TESTEQUAL(a, b) TESTOPERATOR(a, b, _eq) +#define TESTNE(a, b) TESTOPERATOR(a, b, _ne) +#define TESTGE(a, b) TESTOPERATOR(a, b, _ge) + +/* For testing a syscall that returns 0 on success and sets errno otherwise */ +#define TESTSYSCALL(statement) TESTCONDERR((statement) == 0) + +static inline void print_bytes(const void *data, size_t size) +{ + const char *bytes = data; + int i; + + for (i = 0; i < size; ++i) { + if (i % 0x10 == 0) + printf("%08x:", i); + printf("%02x ", (unsigned int) (unsigned char) bytes[i]); + if (i % 0x10 == 0x0f) + printf("\n"); + } + + if (i % 0x10 != 0) + printf("\n"); +} + + + +#endif diff --git a/tools/testing/selftests/filesystems/fuse/test_fuse.h b/tools/testing/selftests/filesystems/fuse/test_fuse.h new file mode 100644 index 000000000000..ca22b26775a0 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/test_fuse.h @@ -0,0 +1,494 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Google LLC + */ + +#ifndef TEST_FUSE__H +#define TEST_FUSE__H + +#define _GNU_SOURCE + +#include "test_framework.h" + +#include +#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 size, + ssize_t *ret_size); +int s_listxattr(struct s pathname, void *list, size_t size, ssize_t *ret_size); +int s_setxattr(struct s pathname, const char name[], const void *value, + size_t size, int flags); +int s_removexattr(struct s pathname, const char name[]); +int s_rename(struct s oldpathname, struct s newpathname); + +struct s tracing_folder(void); +int tracing_on(void); + +char *concat_file_name(const char *dir, const char *file); +char *setup_mount_dir(const char *name); +int delete_dir_tree(const char *dir_path, bool remove_root); + +#define TESTFUSEINNULL(_opcode) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + ssize_t res = read(fuse_dev, &bytes_in, \ + sizeof(bytes_in)); \ + \ + TESTEQUAL(in_header->opcode, _opcode); \ + TESTEQUAL(res, sizeof(*in_header)); \ + } while (false) + +static inline void print_header(struct fuse_in_header *header) +{ + printf("~~HEADER~~"); + printf("len:\t%d\n", header->len); + printf("opcode:\t%d\n", header->opcode); + printf("unique:\t%ld\n", header->unique); + printf("nodeid:\t%ld\n", header->nodeid); + printf("uid:\t%d\n", header->uid); + printf("gid:\t%d\n", header->gid); + printf("pid:\t%d\n", header->pid); + printf("total_extlen:\t%d\n", header->total_extlen); + printf("padding:\t%d\n", header->padding); +} + +static inline int test_fuse_in(int fuse_dev, uint8_t *bytes_in, int opcode, int size) +{ + struct fuse_in_header *in_header = + (struct fuse_in_header *)bytes_in; + ssize_t res = read(fuse_dev, &bytes_in, + sizeof(bytes_in)); + + TESTEQUAL(res, sizeof(*in_header) + size); + TESTEQUAL(in_header->opcode, opcode); + return 0; +out: + return -1; +} + +#define ERR_IN_EXT_LEN (FUSE_REC_ALIGN(sizeof(struct fuse_ext_header) + sizeof(uint32_t))) + +#define TESTFUSEIN(_opcode, in_struct) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + ssize_t res = read(fuse_dev, &bytes_in, \ + sizeof(bytes_in)); \ + \ + TESTEQUAL(res, sizeof(*in_header) + sizeof(*in_struct));\ + TESTEQUAL(in_header->opcode, _opcode); \ + in_struct = (void *)(bytes_in + sizeof(*in_header)); \ + } while (false) + +#define TESTFUSEIN_ERR_IN(_opcode, in_struct, err_in) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_ext_header *ext_h; \ + ssize_t res = read(fuse_dev, &bytes_in, \ + sizeof(bytes_in)); \ + \ + TESTEQUAL(res, sizeof(*in_header) + sizeof(*in_struct) \ + + ERR_IN_EXT_LEN); \ + TESTEQUAL(in_header->opcode, _opcode); \ + in_struct = (void *)(bytes_in + sizeof(*in_header)); \ + ext_h = (void *)&bytes_in[in_header->len \ + - in_header->total_extlen * 8]; \ + err_in = (void *)&ext_h[1]; \ + } while (false) + +#define TESTFUSEIN2(_opcode, in_struct1, in_struct2) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + ssize_t res = read(fuse_dev, &bytes_in, \ + sizeof(bytes_in)); \ + \ + TESTEQUAL(res, sizeof(*in_header) + sizeof(*in_struct1) \ + + sizeof(*in_struct2)); \ + TESTEQUAL(in_header->opcode, _opcode); \ + in_struct1 = (void *)(bytes_in + sizeof(*in_header)); \ + in_struct2 = (void *)(bytes_in + sizeof(*in_header) \ + + sizeof(*in_struct1)); \ + } while (false) + +#define TESTFUSEIN2_ERR_IN(_opcode, in_struct1, in_struct2, err_in) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_ext_header *ext_h; \ + ssize_t res = read(fuse_dev, &bytes_in, \ + sizeof(bytes_in)); \ + \ + TESTEQUAL(res, sizeof(*in_header) + sizeof(*in_struct1) \ + + sizeof(*in_struct2) \ + + ERR_IN_EXT_LEN); \ + TESTEQUAL(in_header->opcode, _opcode); \ + in_struct1 = (void *)(bytes_in + sizeof(*in_header)); \ + in_struct2 = (void *)(bytes_in + sizeof(*in_header) \ + + sizeof(*in_struct1)); \ + ext_h = (void *)&bytes_in[in_header->len \ + - in_header->total_extlen * 8]; \ + err_in = (void *)&ext_h[1]; \ + } while (false) + +#define TESTFUSEINEXT(_opcode, in_struct, extra) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + ssize_t res = read(fuse_dev, &bytes_in, \ + sizeof(bytes_in)); \ + \ + TESTEQUAL(in_header->opcode, _opcode); \ + TESTEQUAL(res, \ + sizeof(*in_header) + sizeof(*in_struct) + extra);\ + in_struct = (void *)(bytes_in + sizeof(*in_header)); \ + } while (false) + +#define TESTFUSEINUNKNOWN() \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + ssize_t res = read(fuse_dev, &bytes_in, \ + sizeof(bytes_in)); \ + \ + TESTGE(res, sizeof(*in_header)); \ + TESTEQUAL(in_header->opcode, -1); \ + } while (false) + +/* Special case lookup since it is asymmetric */ +#define TESTFUSELOOKUP(expected, filter) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + char *name = (char *) (bytes_in + sizeof(*in_header)); \ + ssize_t res; \ + \ + TEST(res = read(fuse_dev, &bytes_in, sizeof(bytes_in)), \ + res != -1); \ + /* TODO once we handle forgets properly, remove */ \ + if (in_header->opcode == FUSE_FORGET) \ + continue; \ + if (in_header->opcode == FUSE_BATCH_FORGET) \ + continue; \ + TESTGE(res, sizeof(*in_header)); \ + TESTEQUAL(in_header->opcode, \ + FUSE_LOOKUP | filter); \ + /* Post filter only recieves fuse_bpf_entry_out if it's \ + * filled in. TODO: Should we populate this for user \ + * postfilter, and if so, how to handle backing? */ \ + TESTEQUAL(res, \ + sizeof(*in_header) + strlen(expected) + 1 + \ + (filter == FUSE_POSTFILTER ? \ + sizeof(struct fuse_entry_out) + \ + sizeof(struct fuse_bpf_entry_out) * 0 + \ + ERR_IN_EXT_LEN: 0)); \ + TESTCOND(!strcmp(name, expected)); \ + break; \ + } while (true) + +/* Special case lookup since it is asymmetric */ +#define TESTFUSELOOKUP_POST_ERRIN(expected, err_in) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_ext_header *ext_h; \ + char *name = (char *) (bytes_in + sizeof(*in_header)); \ + ssize_t res; \ + \ + TEST(res = read(fuse_dev, &bytes_in, sizeof(bytes_in)), \ + res != -1); \ + /* TODO once we handle forgets properly, remove */ \ + if (in_header->opcode == FUSE_FORGET) \ + continue; \ + if (in_header->opcode == FUSE_BATCH_FORGET) \ + continue; \ + TESTGE(res, sizeof(*in_header)); \ + TESTEQUAL(in_header->opcode, \ + FUSE_LOOKUP | FUSE_POSTFILTER); \ + /* Post filter only recieves fuse_bpf_entry_out if it's \ + * filled in. TODO: Should we populate this for user \ + * postfilter, and if so, how to handle backing? */ \ + TESTEQUAL(res, \ + sizeof(*in_header) + strlen(expected) + 1 + \ + sizeof(struct fuse_entry_out) + \ + sizeof(struct fuse_bpf_entry_out) * 0 + \ + ERR_IN_EXT_LEN); \ + TESTCOND(!strcmp(name, expected)); \ + \ + ext_h = (void *)&bytes_in[in_header->len \ + - in_header->total_extlen * 8]; \ + err_in = (void *)&ext_h[1]; \ + break; \ + } while (true) + +#define TESTFUSEOUTEMPTY() \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_out_header *out_header = \ + (struct fuse_out_header *)bytes_out; \ + \ + *out_header = (struct fuse_out_header) { \ + .len = sizeof(*out_header), \ + .unique = in_header->unique, \ + }; \ + TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), \ + out_header->len); \ + } while (false) + +#define TESTFUSEOUTERROR(errno) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_out_header *out_header = \ + (struct fuse_out_header *)bytes_out; \ + \ + *out_header = (struct fuse_out_header) { \ + .len = sizeof(*out_header), \ + .error = errno, \ + .unique = in_header->unique, \ + }; \ + TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), \ + out_header->len); \ + } while (false) + +#define TESTFUSEOUTREAD(data, length) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_out_header *out_header = \ + (struct fuse_out_header *)bytes_out; \ + \ + *out_header = (struct fuse_out_header) { \ + .len = sizeof(*out_header) + length, \ + .unique = in_header->unique, \ + }; \ + memcpy(bytes_out + sizeof(*out_header), data, length); \ + TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), \ + out_header->len); \ + } while (false) + +#define TESTFUSEDIROUTREAD(read_out, data, length) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_out_header *out_header = \ + (struct fuse_out_header *)bytes_out; \ + \ + *out_header = (struct fuse_out_header) { \ + .len = sizeof(*out_header) + \ + sizeof(*read_out) + length, \ + .unique = in_header->unique, \ + }; \ + memcpy(bytes_out + sizeof(*out_header) + \ + sizeof(*read_out), data, length); \ + memcpy(bytes_out + sizeof(*out_header), \ + read_out, sizeof(*read_out)); \ + TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), \ + out_header->len); \ + } while (false) + +#define TESTFUSEOUT1(type1, obj1) \ + do { \ + *(struct fuse_out_header *) bytes_out \ + = (struct fuse_out_header) { \ + .len = sizeof(struct fuse_out_header) \ + + sizeof(struct type1), \ + .unique = ((struct fuse_in_header *) \ + bytes_in)->unique, \ + }; \ + *(struct type1 *) (bytes_out \ + + sizeof(struct fuse_out_header)) \ + = obj1; \ + TESTEQUAL(write(fuse_dev, bytes_out, \ + ((struct fuse_out_header *)bytes_out)->len), \ + ((struct fuse_out_header *)bytes_out)->len); \ + } while (false) + +#define SETFUSEOUT2(type1, obj1, type2, obj2) \ + do { \ + *(struct fuse_out_header *) bytes_out \ + = (struct fuse_out_header) { \ + .len = sizeof(struct fuse_out_header) \ + + sizeof(struct type1) \ + + sizeof(struct type2), \ + .unique = ((struct fuse_in_header *) \ + bytes_in)->unique, \ + }; \ + *(struct type1 *) (bytes_out \ + + sizeof(struct fuse_out_header)) \ + = obj1; \ + *(struct type2 *) (bytes_out \ + + sizeof(struct fuse_out_header) \ + + sizeof(struct type1)) \ + = obj2; \ + } while (false) + +#define TESTFUSEOUT2(type1, obj1, type2, obj2) \ + do { \ + SETFUSEOUT2(type1, obj1, type2, obj2); \ + TESTEQUAL(write(fuse_dev, bytes_out, \ + ((struct fuse_out_header *)bytes_out)->len), \ + ((struct fuse_out_header *)bytes_out)->len); \ + } while (false) + +#define TESTFUSEOUT2_IOCTL(type1, obj1, type2, obj2) \ + do { \ + SETFUSEOUT2(type1, obj1, type2, obj2); \ + TESTEQUAL(ioctl(fuse_dev, \ + FUSE_DEV_IOC_BPF_RESPONSE( \ + ((struct fuse_out_header *)bytes_out)->len), \ + bytes_out), \ + ((struct fuse_out_header *)bytes_out)->len); \ + } while (false) + +#define SETFUSEOUT3(type1, obj1, type2, obj2, type3, obj3) \ + do { \ + *(struct fuse_out_header *) bytes_out \ + = (struct fuse_out_header) { \ + .len = sizeof(struct fuse_out_header) \ + + sizeof(struct type1) \ + + sizeof(struct type2) \ + + sizeof(struct type3), \ + .unique = ((struct fuse_in_header *) \ + bytes_in)->unique, \ + }; \ + *(struct type1 *) (bytes_out \ + + sizeof(struct fuse_out_header)) \ + = obj1; \ + *(struct type2 *) (bytes_out \ + + sizeof(struct fuse_out_header) \ + + sizeof(struct type1)) \ + = obj2; \ + *(struct type3 *) (bytes_out \ + + sizeof(struct fuse_out_header) \ + + sizeof(struct type1) \ + + sizeof(struct type2)) \ + = obj3; \ + } while (false) + +#define TESTFUSEOUT3(type1, obj1, type2, obj2, type3, obj3) \ + do { \ + SETFUSEOUT3(type1, obj1, type2, obj2, type3, obj3); \ + TESTEQUAL(write(fuse_dev, bytes_out, \ + ((struct fuse_out_header *)bytes_out)->len), \ + ((struct fuse_out_header *)bytes_out)->len); \ + } while (false) + +#define TESTFUSEOUT3_FAIL(type1, obj1, type2, obj2, type3, obj3) \ + do { \ + SETFUSEOUT3(type1, obj1, type2, obj2, type3, obj3); \ + TESTEQUAL(write(fuse_dev, bytes_out, \ + ((struct fuse_out_header *)bytes_out)->len), \ + -1); \ + } while (false) + +#define FUSE_DEV_IOC_BPF_RESPONSE(N) _IOW(FUSE_DEV_IOC_MAGIC, 125, char[N]) + +#define TESTFUSEOUT3_IOCTL(type1, obj1, type2, obj2, type3, obj3) \ + do { \ + SETFUSEOUT3(type1, obj1, type2, obj2, type3, obj3); \ + TESTEQUAL(ioctl(fuse_dev, \ + FUSE_DEV_IOC_BPF_RESPONSE( \ + ((struct fuse_out_header *)bytes_out)->len), \ + bytes_out), \ + ((struct fuse_out_header *)bytes_out)->len); \ + } while (false) + +#define TESTFUSEINITFLAGS(fuse_connection_flags) \ + do { \ + DECL_FUSE_IN(init); \ + \ + TESTFUSEIN(FUSE_INIT, init_in); \ + TESTEQUAL(init_in->major, FUSE_KERNEL_VERSION); \ + TESTEQUAL(init_in->minor, FUSE_KERNEL_MINOR_VERSION); \ + TESTFUSEOUT1(fuse_init_out, ((struct fuse_init_out) { \ + .major = FUSE_KERNEL_VERSION, \ + .minor = FUSE_KERNEL_MINOR_VERSION, \ + .max_readahead = 4096, \ + .flags = fuse_connection_flags, \ + .max_background = 0, \ + .congestion_threshold = 0, \ + .max_write = 4096, \ + .time_gran = 1000, \ + .max_pages = 12, \ + .map_alignment = 4096, \ + })); \ + } while (false) + +#define TESTFUSEINIT() \ + TESTFUSEINITFLAGS(0) + +#define DECL_FUSE_IN(name) \ + struct fuse_##name##_in *name##_in = \ + (struct fuse_##name##_in *) \ + (bytes_in + sizeof(struct fuse_in_header)) + +#define DECL_FUSE(name) \ + struct fuse_##name##_in *name##_in __attribute__((unused)); \ + struct fuse_##name##_out *name##_out __attribute__((unused)) + +#define FUSE_ACTION TEST(pid = fork(), pid != -1); \ + if (pid) { + +#define FUSE_DAEMON } else { \ + uint8_t bytes_in[FUSE_MIN_READ_BUFFER] \ + __attribute__((unused)); \ + uint8_t bytes_out[FUSE_MIN_READ_BUFFER] \ + __attribute__((unused)); + +#define FUSE_DONE exit(TEST_SUCCESS); \ + } \ + TESTEQUAL(waitpid(pid, &status, 0), pid); \ + TESTEQUAL(status, TEST_SUCCESS); + +int mount_fuse(const char *mount_dir, const char *bpf_name, int dir_fd, + int *fuse_dev_ptr); +int mount_fuse_no_init(const char *mount_dir, const char *bpf_name, int dir_fd, + int *fuse_dev_ptr); +#endif From patchwork Fri Mar 29 01:53:51 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Daniel Rosenberg X-Patchwork-Id: 13609912 Received: from mail-yw1-f201.google.com (mail-yw1-f201.google.com [209.85.128.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BCE8313C827 for ; Fri, 29 Mar 2024 01:55:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677324; cv=none; b=c1owYkBhaCL/Eao7cbeK00irGbgJjzLPai43RSTJ0NwUDXWib4i7iw/z56AWfzM5rJHGpqHWpU+0vpTnbwxpzobAeNOWlxk+K5zXfD8ZY0r3pmCq2xzWjz/xxF+peO31zJKCVQkwUz6vapPT1RSqpOJahyRUqrDi1mOry4cMnGs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1711677324; c=relaxed/simple; bh=QYiNaoP0wgjmdl7KYo7r8xxfmKhoKe/kT/EhiLUTu0o=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=JahoQEecD3g+vD60v3tef5IlwOBNARPXaffK6iSKrgdvTbZtAs5Rk1Zy3ghv4DuJ19//iI9raJ2vJbiRr/HD/WvJKMa9CKFScsBl6dpab4WDVezpXWLuHzs0Y51DQeqz8xO+GVdC/ezcYej9xtBT63kEZAkMf4N6cplLfmjGD/o= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=zSeGM2Oe; arc=none smtp.client-ip=209.85.128.201 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--drosen.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="zSeGM2Oe" Received: by mail-yw1-f201.google.com with SMTP id 00721157ae682-60a0151f194so27694887b3.1 for ; Thu, 28 Mar 2024 18:55:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1711677320; x=1712282120; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=uUsaTtgkbErn3BD3WrF1FB8TcQrwq3YumJAvXSLdPcQ=; b=zSeGM2OeJgyM4PeXDAJaiPVsLJx+U7ndpvAssuJK0jVaEMzucfi/14YPsyKU+s8Lu2 lD8GPe4yfscOaQHuTPssmlaFNEK/RSmq/94ZbsuuRSIfSdsVAJX05f28PVwcgoRKwR9P bs0cGXh1LkW0v9Krn8My53D1BtNRs6Yt7+H+d1/h1zyeXPD6zvFRZwoJTGP0PER5Ow/+ VicZy4gPL4+cqrrFH6XijIeUYmqKMTTfOWJszuJI2/cWK7LVLkDfsgvY+4lRshpvvqhp iZnwLFuE0cQotQ5WbOWS3zTzi4uzELLOAoqqdVx09EYCJYQzQRSMqHcltl7LnvpRiF9V 9GHg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711677320; x=1712282120; 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=uUsaTtgkbErn3BD3WrF1FB8TcQrwq3YumJAvXSLdPcQ=; b=ask5ViGdv3vdqZNMaKSjO3VFmlNRRwCdwi8RmDLGY4ZDo8Hd5qoejxIEOOhUSWtfH6 pPjaQM5+Er90n/NcyDdVZjnWM3qNvNI/8+kio+S8XErdWb4Rk+CfJMZnr5Sq21glEBar 3EGKPzJJuOM3yENN0tB2437P88G7Bn4wRQjC3vnwb7I1nj0dhTGTQN5XF7OOjHFRe+LX 2Tej+UbkuKtrqc3WgiICvLYx0HJk281J4aYxpOjubZrGIcFyZGkwPTK5bggIe37pTqJD BjVywA7Mgz64cxv8W6LuerRUlIbaIhZdIwLRs3TuisfQBrg+Hci56kklEBCoWBnvcrvT lTeA== X-Forwarded-Encrypted: i=1; AJvYcCWMlykB6K5mMC1Gx0e09F+l8Mo4LP34qmwinJBuV4BfV1D6/j+ve85WwxUJDqTiS07X0/jt1rpXzFpIEkj+qC2dYXrU X-Gm-Message-State: AOJu0Yw6hNDB1DTW7AxDPg+qNVLNFMAAaCbEHIzH8rWE9s64S6PCtMxN PKQVoWysXTg0JpUiIYav1+E2iahAVfjVn5O/I1JiuFpkyppELp5XMF0SzqQtVsjt0IYKY9BXbZG dHQ== X-Google-Smtp-Source: AGHT+IHHRmevfoLaT4O3ZiZOOorIZcG6OlW64J4uxzsicKT/QzpKc+1r71hBTI36u3UKAsk58C0KEcDo3Qw= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:fcce:d6ab:804c:b94b]) (user=drosen job=sendgmr) by 2002:a0d:ca01:0:b0:60a:16ae:38ee with SMTP id m1-20020a0dca01000000b0060a16ae38eemr282231ywd.3.1711677319906; Thu, 28 Mar 2024 18:55:19 -0700 (PDT) Date: Thu, 28 Mar 2024 18:53:51 -0700 In-Reply-To: <20240329015351.624249-1-drosen@google.com> Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20240329015351.624249-1-drosen@google.com> X-Mailer: git-send-email 2.44.0.478.gd926399ef9-goog Message-ID: <20240329015351.624249-37-drosen@google.com> Subject: [RFC PATCH v4 36/36] 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 , Eduard Zingerman , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , Christian Brauner , kernel-team@android.com, Daniel Rosenberg X-Patchwork-State: RFC 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 67d70b3c4abb..cf9555e4be1f 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -2184,16 +2184,83 @@ static void fuse_fs_cleanup(void) static struct kobject *fuse_kobj; +static char struct_op_name[BPF_FUSE_NAME_MAX]; +static struct fuse_ops *fop = NULL; + +static ssize_t struct_op_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + size_t max = count; + + if (max > BPF_FUSE_NAME_MAX) max = BPF_FUSE_NAME_MAX; + strncpy(struct_op_name, buf, max); + if (struct_op_name[max-1] == '\n') + struct_op_name[max-1] = 0; + put_fuse_ops(fop); + fop = 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 = 0; + struct bpf_fuse_meta_info meta; + struct fuse_mkdir_in in; + struct fuse_buffer name; + char name_buff[10] = "test"; + + name.data = &name_buff[0]; + name.flags = BPF_FUSE_VARIABLE_SIZE; + name.max_size = 10; + name.size = 5; + + op = fop; + if (!op) { + printk("Could not find fuse_op for %s", struct_op_name); + return 0; + } + + if (op->mkdir_prefilter) + result = op->mkdir_prefilter(&meta, &in, &name); + else + printk("No func!!"); + + printk("in->mode:%d, name:%s result:%d", in.mode, (char *)name.data, result); + return sprintf(buf, "%d dyn:%s\n", result, (char *)name.data); +} + +static struct kobj_attribute test_attr = __ATTR_RW(struct_op); + +static struct attribute *test_attrs[] = { + &test_attr.attr, + NULL, +}; + +static const struct attribute_group test_attr_group = { + .attrs = test_attrs, +}; + static int fuse_sysfs_init(void) { int err; + memset(struct_op_name, 0, BPF_FUSE_NAME_MAX); fuse_kobj = kobject_create_and_add("fuse", fs_kobj); if (!fuse_kobj) { err = -ENOMEM; goto out_err; } + err = sysfs_create_group(fuse_kobj, &test_attr_group); + if (err) + goto tmp; + err = sysfs_create_mount_point(fuse_kobj, "connections"); if (err) goto out_fuse_unregister; @@ -2202,6 +2269,8 @@ static int fuse_sysfs_init(void) out_fuse_unregister: kobject_put(fuse_kobj); +tmp: + sysfs_remove_group(fuse_kobj, &test_attr_group); out_err: return err; } @@ -2209,6 +2278,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); } diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/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:=$(TOOLSDIR)/testing/selftests/ LDLIBS := -lpthread -lelf -lz TEST_GEN_PROGS := fuse_test fuse_daemon TEST_GEN_FILES := \ + struct_op_test.bpf.o \ test.skel.h \ fd.sh \ 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") = "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, struct 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 = 0; + struct bpf_dynptr name_ptr; + char *name_buf; + //char dummy[7] = {}; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + name_buf = 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; + + //if (bpf_fuse_namecmp(name, "test", 4) == 0) + // return 42; + + return res; +} + +SEC(".struct_ops") +struct fuse_ops test_ops = { + .mkdir_prefilter = (void *)test_func, + .name = "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_info *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_info *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_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) +{ + bpf_printk("create_open_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, release_prefilter, const struct bpf_fuse_meta_info *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_info *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_info *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_info *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_info *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_info *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_info *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_info *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_meta_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_info *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_meta_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_info *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_info *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_info *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_info *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_info *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_info *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_info *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_info *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_info *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_info *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_info *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_info *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_info *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_info *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_info *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_info *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_info *meta, + const struct fuse_access_in *in) +{ + bpf_printk("access_postfilter"); + return BPF_FUSE_CONTINUE; +} + +SEC(".struct_ops") +struct fuse_ops trace_ops = { + .open_prefilter = (void *)open_prefilter, + .open_postfilter = (void *)open_postfilter, + + .opendir_prefilter = (void *)opendir_prefilter, + .opendir_postfilter = (void *)opendir_postfilter, + + .create_open_prefilter = (void *)create_open_prefilter, + .create_open_postfilter = (void *)create_open_postfilter, + + .release_prefilter = (void *)release_prefilter, + .release_postfilter = (void *)release_postfilter, + + .releasedir_prefilter = (void *)releasedir_prefilter, + .releasedir_postfilter = (void *)releasedir_postfilter, + + .flush_prefilter = (void *)flush_prefilter, + .flush_postfilter = (void *)flush_postfilter, + + .lseek_prefilter = (void *)lseek_prefilter, + .lseek_postfilter = (void *)lseek_postfilter, + + .copy_file_range_prefilter = (void *)copy_file_range_prefilter, + .copy_file_range_postfilter = (void *)copy_file_range_postfilter, + + .fsync_prefilter = (void *)fsync_prefilter, + .fsync_postfilter = (void *)fsync_postfilter, + + .dir_fsync_prefilter = (void *)dir_fsync_prefilter, + .dir_fsync_postfilter = (void *)dir_fsync_postfilter, + + .getxattr_prefilter = (void *)getxattr_prefilter, + .getxattr_postfilter = (void *)getxattr_postfilter, + + .listxattr_prefilter = (void *)listxattr_prefilter, + .listxattr_postfilter = (void *)listxattr_postfilter, + + .setxattr_prefilter = (void *)setxattr_prefilter, + .setxattr_postfilter = (void *)setxattr_postfilter, + + .removexattr_prefilter = (void *)removexattr_prefilter, + .removexattr_postfilter = (void *)removexattr_postfilter, + + .read_iter_prefilter = (void *)read_iter_prefilter, + .read_iter_postfilter = (void *)read_iter_postfilter, + + .write_iter_prefilter = (void *)write_iter_prefilter, + .write_iter_postfilter = (void *)write_iter_postfilter, + + .file_fallocate_prefilter = (void *)file_fallocate_prefilter, + .file_fallocate_postfilter = (void *)file_fallocate_postfilter, + + .lookup_prefilter = (void *)lookup_prefilter, + .lookup_postfilter = (void *)lookup_postfilter, + + .mknod_prefilter = (void *)mknod_prefilter, + .mknod_postfilter = (void *)mknod_postfilter, + + .mkdir_prefilter = (void *)mkdir_prefilter, + .mkdir_postfilter = (void *)mkdir_postfilter, + + .rmdir_prefilter = (void *)rmdir_prefilter, + .rmdir_postfilter = (void *)rmdir_postfilter, + + .rename2_prefilter = (void *)rename2_prefilter, + .rename2_postfilter = (void *)rename2_postfilter, + + .rename_prefilter = (void *)rename_prefilter, + .rename_postfilter = (void *)rename_postfilter, + + .unlink_prefilter = (void *)unlink_prefilter, + .unlink_postfilter = (void *)unlink_postfilter, + + .link_prefilter = (void *)link_prefilter, + .link_postfilter = (void *)link_postfilter, + + .getattr_prefilter = (void *)getattr_prefilter, + .getattr_postfilter = (void *)getattr_postfilter, + + .setattr_prefilter = (void *)setattr_prefilter, + .setattr_postfilter = (void *)setattr_postfilter, + + .statfs_prefilter = (void *)statfs_prefilter, + .statfs_postfilter = (void *)statfs_postfilter, + + .get_link_prefilter = (void *)get_link_prefilter, + .get_link_postfilter = (void *)get_link_postfilter, + + .symlink_prefilter = (void *)symlink_prefilter, + .symlink_postfilter = (void *)symlink_postfilter, + + .readdir_prefilter = (void *)readdir_prefilter, + .readdir_postfilter = (void *)readdir_postfilter, + + .access_prefilter = (void *)access_prefilter, + .access_postfilter = (void *)access_postfilter, + + .name = "trace_pre_ops", +};