From nobody Thu Oct 9 10:42:44 2025 Received: from mail-pj1-f73.google.com (mail-pj1-f73.google.com [209.85.216.73]) (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 706E62957CE for ; Tue, 17 Jun 2025 22:15:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.73 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750198522; cv=none; b=kD1OfQfiHGVe5Cz0sh+UyTMyges39+OTXHNrVXvfNkON02cN+XT12W5ibPWm7bgr+zwpptsZl+sSdRy0E34g85/XsMrVqUPVQK6PfABVi9whWAfdZej+Q8iAVe+gtDVM2eJt0nj4LMauiqKXcpBr/UKwxOGvQomu/L8X6tuPBL4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750198522; c=relaxed/simple; bh=O41hjxZb2zNX12penWg+5xyEjdqJhMQtpNuAo7I0PHM=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=m9L24jUsxcn5YR/7CnkiVOipR7Fho/LNJNYD9n/mgXYSS1j8W3/bSAm/79pWgB3cmZ2f4oijDktMPaDEDin5qAiEJBtdnb+HJFhQo3gbwNzAgIL5lWt+yhy+5tm8byKnZS2xiRj9hYdg69rH3Mi7XoWaL9J8ZUD2t61CcxPA0eo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--paullawrence.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=k3uvYWxb; arc=none smtp.client-ip=209.85.216.73 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--paullawrence.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="k3uvYWxb" Received: by mail-pj1-f73.google.com with SMTP id 98e67ed59e1d1-313d6d671ffso4887700a91.2 for ; Tue, 17 Jun 2025 15:15:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1750198520; x=1750803320; 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=97cWcMp3hRxx2+tQ1zjI435kFk+yFY19PKnRazUc70E=; b=k3uvYWxbv+7WxBTY03WYE+LPtHHkVRbQQWHC2cU+XoNC2ZQ0dNf8RnOdIJfxwtag6u PVwSCDun2+xHSsqPV/vY6FRXJ50RG/5bHJcv/idDRlQeCgg3YZMpMxQFcLHGa9ehLomU 8Y60AS8sdYyq9fOlVmSOVuHDQUDpUjCGUEKgiLcfpbm/iMf83US6WckIw+zcnqApUHTZ M5xK6gYedzkN/dW6JABkB719k9lFKH91F6ojvzbwCAil/4OU/p8laywOoQQ/YqMbZAWI UAq30n1yb5GIcb+bXkVht/m2OK+WZ5e3amBgOWkeBPUSceKBBAFwI4SajSzULbsfcIoA 67jg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1750198520; x=1750803320; 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=97cWcMp3hRxx2+tQ1zjI435kFk+yFY19PKnRazUc70E=; b=oUeEH1HDpyfJpzLA4Tx9yQ2kJut7uhXdppaViy4zLjIw7ktmgC6drQYgjtpSA+DVRP 6aw9sBPTWjb+Xgks0xfzpaS6Gf4xp0aXaUrH0DC6+TJ3Qwib2EKw4r/qMK6CB1/mz+wQ 51fnw6HanbDsPNDGSpCgyvHTIcT4m0kVGQ6zN4aTM/Cd/RMJLopPkSzP0QV+6Es3nh4s XQUeLJiNPNfVXLoNm+ixPqvbRhq9t2uHk2oiJl6uKxXV2NbEARSr2Ot/2EkZ2wMKdmFw SYN8sR2xktv9C3DWnjDS2/iM+FyKtHLooNLc0aEZrpvA2NrTDbww8Esbca5PBOATCnZ5 ONRQ== X-Gm-Message-State: AOJu0YyTDmtL9KVfWnw1Yr9QuEItoQdA2X9Ae3qSq4mKGSc7gxG5Y8vy eo3e4FXewL+RTBp4affHKIWyqNQddazUWIXIY382PTu1RVk8AlWunwUl8zwVkDdHGu4JuyzKg/5 a7+LM9tg8/gl7u6wHnBHF2yssWzoFuA== X-Google-Smtp-Source: AGHT+IFZbZ0OAMbQxGShThyDpRqMVZfcRiXmXP1n+/duJAPeyrLyprq+2mMSPqtUBPH5wtnUtgK2/k+GprSC1MmG+OU= X-Received: from pjboo14.prod.google.com ([2002:a17:90b:1c8e:b0:314:3438:8e79]) (user=paullawrence job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90b:2dd2:b0:312:f88d:25f9 with SMTP id 98e67ed59e1d1-313f1c7dacfmr24384558a91.7.1750198519778; Tue, 17 Jun 2025 15:15:19 -0700 (PDT) Date: Tue, 17 Jun 2025 15:14:53 -0700 In-Reply-To: <20250617221456.888231-1-paullawrence@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250617221456.888231-1-paullawrence@google.com> X-Mailer: git-send-email 2.50.0.rc2.696.g1fc2a0284f-goog Message-ID: <20250617221456.888231-2-paullawrence@google.com> Subject: [PATCH v1 1/2] fuse: Add backing file option to lookup From: Paul Lawrence To: Miklos Szeredi Cc: linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Paul Lawrence Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This commit starts the process of adding directory passthrough to fuse. * Add the Kconfig option CONFIG_FUSE_PASSTHROUGH_DIR * Add struct fuse_entry_backing_out, an optional second out arg to FUSE_LOOKUP. This will contain the fd to the backing file or directory * Synchronously store the backing file in the args * Add backing inode to fuse inode and backing path to fuse dentry * If FUSE_LOOKUP returns a backing fd, store path and inode Signed-off-by: Paul Lawrence --- fs/fuse/Kconfig | 13 +++++ fs/fuse/Makefile | 1 + fs/fuse/backing.c | 29 ++++++++++ fs/fuse/dev.c | 14 +++++ fs/fuse/dir.c | 114 +++++++++++++++++++++++++++++--------- fs/fuse/fuse_i.h | 22 +++++++- fs/fuse/inode.c | 114 ++++++++++++++++++++++++++++++++++---- include/uapi/linux/fuse.h | 4 ++ 8 files changed, 273 insertions(+), 38 deletions(-) create mode 100644 fs/fuse/backing.c diff --git a/fs/fuse/Kconfig b/fs/fuse/Kconfig index ca215a3cba3e..b8a05cb427df 100644 --- a/fs/fuse/Kconfig +++ b/fs/fuse/Kconfig @@ -75,3 +75,16 @@ config FUSE_IO_URING =20 If you want to allow fuse server/client communication through io-uring, answer Y + +config FUSE_PASSTHROUGH_DIR + bool "FUSE directory passthrough operations support" + depends on FUSE_FS + select FS_STACK + help + This allows bypassing FUSE server on a specified directory by mapping + all FUSE operations to be performed directly on a backing directory. + + The bypass will automatically extend to all sub directories and files + in that directory. + + If you want to allow directory passthrough operations, answer Y. diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile index 3f0f312a31c1..79ca3a68b993 100644 --- a/fs/fuse/Makefile +++ b/fs/fuse/Makefile @@ -16,5 +16,6 @@ fuse-$(CONFIG_FUSE_DAX) +=3D dax.o fuse-$(CONFIG_FUSE_PASSTHROUGH) +=3D passthrough.o fuse-$(CONFIG_SYSCTL) +=3D sysctl.o fuse-$(CONFIG_FUSE_IO_URING) +=3D dev_uring.o +fuse-$(CONFIG_FUSE_PASSTHROUGH_DIR) +=3D backing.o =20 virtiofs-y :=3D virtio_fs.o diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c new file mode 100644 index 000000000000..1dcc617bf660 --- /dev/null +++ b/fs/fuse/backing.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FUSE-BPF: Filesystem in Userspace with BPF + * Copyright (c) 2021 Google LLC + */ + +#include "fuse_i.h" + +int fuse_handle_backing(struct fuse_entry_backing *feb, + struct inode **backing_inode, struct path *backing_path) +{ + struct file *backing_file =3D feb->backing_file; + + if (!backing_file) + return -EINVAL; + if (IS_ERR(backing_file)) + return PTR_ERR(backing_file); + + if (backing_inode) + iput(*backing_inode); + *backing_inode =3D backing_file->f_inode; + ihold(*backing_inode); + + path_put(backing_path); + *backing_path =3D backing_file->f_path; + path_get(backing_path); + + return 0; +} diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 6dcbaa218b7a..db1fbd1fdb85 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -2181,6 +2181,20 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fu= d, err =3D fuse_copy_out_args(cs, req->args, nbytes); fuse_copy_finish(cs); =20 + if (!err && req->in.h.opcode =3D=3D FUSE_LOOKUP && + req->args->out_args[1].size =3D=3D + sizeof(struct fuse_entry_backing_out)) { + struct fuse_entry_backing_out *febo =3D + (struct fuse_entry_backing_out *) + req->args->out_args[1].value; + struct fuse_entry_backing *feb =3D + container_of(febo, struct fuse_entry_backing, out); + + feb->backing_file =3D fget(febo->backing_fd); + if (!feb->backing_file) + err =3D -EBADFD; + } + spin_lock(&fpq->lock); clear_bit(FR_LOCKED, &req->flags); if (!fpq->connected) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 83ac192e7fdd..909463fae94d 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -34,7 +34,7 @@ static void fuse_advise_use_readdirplus(struct inode *dir) set_bit(FUSE_I_ADVISE_RDPLUS, &fi->state); } =20 -#if BITS_PER_LONG >=3D 64 +#if BITS_PER_LONG >=3D 64 && !defined(CONFIG_FUSE_PASSTHROUGH_DIR) static inline void __fuse_dentry_settime(struct dentry *entry, u64 time) { entry->d_fsdata =3D (void *) time; @@ -47,7 +47,12 @@ static inline u64 fuse_dentry_time(const struct dentry *= entry) =20 #else union fuse_dentry { - u64 time; + struct { + u64 time; +#ifdef CONFIG_FUSE_PASSTHROUGH_DIR + struct path backing_path; +#endif + }; struct rcu_head rcu; }; =20 @@ -60,6 +65,11 @@ static inline u64 fuse_dentry_time(const struct dentry *= entry) { return ((union fuse_dentry *) entry->d_fsdata)->time; } + +static inline union fuse_dentry *get_fuse_dentry(const struct dentry *entr= y) +{ + return entry->d_fsdata; +} #endif =20 static void fuse_dentry_settime(struct dentry *dentry, u64 time) @@ -170,7 +180,8 @@ static void fuse_invalidate_entry(struct dentry *entry) =20 static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args, u64 nodeid, const struct qstr *name, - struct fuse_entry_out *outarg) + struct fuse_entry_out *outarg, + struct fuse_entry_backing_out *febo) { memset(outarg, 0, sizeof(struct fuse_entry_out)); args->opcode =3D FUSE_LOOKUP; @@ -181,9 +192,12 @@ static void fuse_lookup_init(struct fuse_conn *fc, str= uct fuse_args *args, args->in_args[1].value =3D name->name; args->in_args[2].size =3D 1; args->in_args[2].value =3D ""; - args->out_numargs =3D 1; + args->out_argvar =3D true; + args->out_numargs =3D 2; args->out_args[0].size =3D sizeof(struct fuse_entry_out); args->out_args[0].value =3D outarg; + args->out_args[1].size =3D sizeof(struct fuse_entry_backing_out); + args->out_args[1].value =3D febo; } =20 /* @@ -209,6 +223,7 @@ static int fuse_dentry_revalidate(struct inode *dir, co= nst struct qstr *name, else 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_entry_backing feb; FUSE_ARGS(args); struct fuse_forget_link *forget; u64 attr_version; @@ -231,7 +246,7 @@ static int fuse_dentry_revalidate(struct inode *dir, co= nst struct qstr *name, attr_version =3D fuse_get_attr_version(fm->fc); =20 fuse_lookup_init(fm->fc, &args, get_node_id(dir), - name, &outarg); + name, &outarg, &feb.out); ret =3D fuse_simple_request(fm, &args); /* Zero nodeid is same as -ENOENT */ if (!ret && !outarg.nodeid) @@ -278,7 +293,7 @@ static int fuse_dentry_revalidate(struct inode *dir, co= nst struct qstr *name, goto out; } =20 -#if BITS_PER_LONG < 64 +#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_PASSTHROUGH_DIR) static int fuse_dentry_init(struct dentry *dentry) { dentry->d_fsdata =3D kzalloc(sizeof(union fuse_dentry), @@ -290,6 +305,11 @@ static void fuse_dentry_release(struct dentry *dentry) { union fuse_dentry *fd =3D dentry->d_fsdata; =20 +#ifdef CONFIG_FUSE_PASSTHROUGH_DIR + if (fd && fd->backing_path.dentry) + path_put(&fd->backing_path); +#endif + kfree_rcu(fd, rcu); } #endif @@ -329,7 +349,7 @@ static struct vfsmount *fuse_dentry_automount(struct pa= th *path) const struct dentry_operations fuse_dentry_operations =3D { .d_revalidate =3D fuse_dentry_revalidate, .d_delete =3D fuse_dentry_delete, -#if BITS_PER_LONG < 64 +#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_PASSTHROUGH_DIR) .d_init =3D fuse_dentry_init, .d_release =3D fuse_dentry_release, #endif @@ -360,10 +380,12 @@ bool fuse_invalid_attr(struct fuse_attr *attr) } =20 int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr= *name, - struct fuse_entry_out *outarg, struct inode **inode) + struct fuse_entry_out *outarg, struct dentry *entry, + struct inode **inode) { struct fuse_mount *fm =3D get_fuse_mount_super(sb); FUSE_ARGS(args); + struct fuse_entry_backing backing_arg =3D {0}; struct fuse_forget_link *forget; u64 attr_version, evict_ctr; int err; @@ -382,23 +404,61 @@ int fuse_lookup_name(struct super_block *sb, u64 node= id, const struct qstr *name attr_version =3D fuse_get_attr_version(fm->fc); evict_ctr =3D fuse_get_evict_ctr(fm->fc); =20 - fuse_lookup_init(fm->fc, &args, nodeid, name, outarg); + fuse_lookup_init(fm->fc, &args, nodeid, name, outarg, &backing_arg.out); err =3D fuse_simple_request(fm, &args); - /* Zero nodeid is same as -ENOENT, but with valid timeout */ - if (err || !outarg->nodeid) - goto out_put_forget; =20 - err =3D -EIO; - if (fuse_invalid_attr(&outarg->attr)) - goto out_put_forget; - if (outarg->nodeid =3D=3D FUSE_ROOT_ID && outarg->generation !=3D 0) { - pr_warn_once("root generation should be zero\n"); - outarg->generation =3D 0; +#ifdef CONFIG_FUSE_PASSTHROUGH_DIR + if (err =3D=3D sizeof(backing_arg.out)) { + struct file *backing_file; + struct inode *backing_inode; + + err =3D -ENOENT; + if (!entry) + goto out_put_forget; + + err =3D -EINVAL; + backing_file =3D backing_arg.backing_file; + if (!backing_file) + goto out_put_forget; + + if (IS_ERR(backing_file)) { + err =3D PTR_ERR(backing_file); + goto out_put_forget; + } + + backing_inode =3D backing_file->f_inode; + *inode =3D fuse_iget_backing(sb, backing_inode); + if (!*inode) + goto out_put_forget; + + err =3D fuse_handle_backing(&backing_arg, + &get_fuse_inode(*inode)->backing_inode, + &get_fuse_dentry(entry)->backing_path); + if (err) { + iput(*inode); + *inode =3D NULL; + goto out_put_forget; + } + } else +#endif + { + /* Zero nodeid is same as -ENOENT, but with valid timeout */ + if (err || !outarg->nodeid) + goto out_put_forget; + + err =3D -EIO; + if (fuse_invalid_attr(&outarg->attr)) + goto out_put_forget; + if (outarg->nodeid =3D=3D FUSE_ROOT_ID && outarg->generation !=3D 0) { + pr_warn_once("root generation should be zero\n"); + outarg->generation =3D 0; + } + + *inode =3D fuse_iget(sb, outarg->nodeid, outarg->generation, + &outarg->attr, ATTR_TIMEOUT(outarg), + attr_version, evict_ctr); } =20 - *inode =3D fuse_iget(sb, outarg->nodeid, outarg->generation, - &outarg->attr, ATTR_TIMEOUT(outarg), - attr_version, evict_ctr); err =3D -ENOMEM; if (!*inode) { fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1); @@ -406,9 +466,11 @@ int fuse_lookup_name(struct super_block *sb, u64 nodei= d, const struct qstr *name } err =3D 0; =20 - out_put_forget: +out_put_forget: kfree(forget); - out: +out: + if (backing_arg.backing_file) + fput(backing_arg.backing_file); return err; } =20 @@ -427,7 +489,7 @@ static struct dentry *fuse_lookup(struct inode *dir, st= ruct dentry *entry, =20 locked =3D fuse_lock_inode(dir); err =3D fuse_lookup_name(dir->i_sb, get_node_id(dir), &entry->d_name, - &outarg, &inode); + &outarg, entry, &inode); fuse_unlock_inode(dir, locked); if (err =3D=3D -ENOENT) { outarg_valid =3D false; @@ -455,9 +517,9 @@ static struct dentry *fuse_lookup(struct inode *dir, st= ruct dentry *entry, fuse_advise_use_readdirplus(dir); return newent; =20 - out_iput: +out_iput: iput(inode); - out_err: +out_err: return ERR_PTR(err); } =20 diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index d56d4fd956db..1dc04bc6ac49 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -106,6 +106,11 @@ struct fuse_backing { struct rcu_head rcu; }; =20 +struct fuse_entry_backing { + struct fuse_entry_backing_out out; + struct file *backing_file; +}; + /** FUSE inode */ struct fuse_inode { /** Inode data */ @@ -213,6 +218,14 @@ struct fuse_inode { /** Reference to backing file in passthrough mode */ struct fuse_backing *fb; #endif + +#ifdef CONFIG_FUSE_PASSTHROUGH_DIR + /** + * Backing inode, if this inode is from a backing file system. + * If this is set, nodeid is 0. + */ + struct inode *backing_inode; +#endif }; =20 /** FUSE inode state bits */ @@ -1114,13 +1127,17 @@ extern const struct dentry_operations fuse_root_den= try_operations; /** * Get a filled in inode */ +struct inode *fuse_iget_backing(struct super_block *sb, + 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, u64 evict_ctr); =20 int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr= *name, - struct fuse_entry_out *outarg, struct inode **inode); + struct fuse_entry_out *outarg, struct dentry *entry, + struct inode **inode); =20 /** * Send FORGET command @@ -1577,4 +1594,7 @@ extern void fuse_sysctl_unregister(void); #define fuse_sysctl_unregister() do { } while (0) #endif /* CONFIG_SYSCTL */ =20 +/* backing.c */ +int fuse_handle_backing(struct fuse_entry_backing *feb, + struct inode **backing_inode, struct path *backing_path); #endif /* _FS_FUSE_I_H */ diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index fd48e8d37f2e..404d8cbe5f25 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -35,6 +35,8 @@ struct list_head fuse_conn_list; DEFINE_MUTEX(fuse_mutex); =20 static int set_global_limit(const char *val, const struct kernel_param *kp= ); +static void fuse_fill_attr_from_inode(struct fuse_attr *attr, + const struct fuse_inode *fi); =20 unsigned int fuse_max_pages_limit =3D 256; /* default is no timeout */ @@ -195,6 +197,16 @@ static void fuse_evict_inode(struct inode *inode) } } =20 +#ifdef CONFIG_FUSE_PASSTHROUGH_DIR +static void fuse_destroy_inode(struct inode *inode) +{ + struct fuse_inode *fi =3D get_fuse_inode(inode); + + if (fi->backing_inode) + iput(fi->backing_inode); +} +#endif + static int fuse_reconfigure(struct fs_context *fsc) { struct super_block *sb =3D fsc->root->d_sb; @@ -443,22 +455,93 @@ static void fuse_init_inode(struct inode *inode, stru= ct fuse_attr *attr, inode->i_acl =3D inode->i_default_acl =3D ACL_DONT_CACHE; } =20 +struct fuse_inode_identifier { + u64 nodeid; + struct inode *backing_inode; +}; + static int fuse_inode_eq(struct inode *inode, void *_nodeidp) { - u64 nodeid =3D *(u64 *) _nodeidp; - if (get_node_id(inode) =3D=3D nodeid) - return 1; - else - return 0; + struct fuse_inode_identifier *fii =3D + (struct fuse_inode_identifier *) _nodeidp; + struct fuse_inode *fi =3D get_fuse_inode(inode); + + return fii->nodeid =3D=3D fi->nodeid; +} + +static int fuse_inode_backing_eq(struct inode *inode, void *_nodeidp) +{ + struct fuse_inode_identifier *fii =3D + (struct fuse_inode_identifier *) _nodeidp; + struct fuse_inode *fi =3D get_fuse_inode(inode); + + return fii->nodeid =3D=3D fi->nodeid + && fii->backing_inode =3D=3D fi->backing_inode; } =20 static int fuse_inode_set(struct inode *inode, void *_nodeidp) { - u64 nodeid =3D *(u64 *) _nodeidp; - get_fuse_inode(inode)->nodeid =3D nodeid; + struct fuse_inode_identifier *fii =3D + (struct fuse_inode_identifier *) _nodeidp; + struct fuse_inode *fi =3D get_fuse_inode(inode); + + fi->nodeid =3D fii->nodeid; + + return 0; +} + +static int fuse_inode_backing_dir_set(struct inode *inode, void *_nodeidp) +{ + struct fuse_inode_identifier *fii =3D + (struct fuse_inode_identifier *) _nodeidp; + struct fuse_inode *fi =3D get_fuse_inode(inode); + + fi->nodeid =3D fii->nodeid; +#ifdef CONFIG_FUSE_PASSTHROUGH_DIR + fi->backing_inode =3D fii->backing_inode; + if (fi->backing_inode) + ihold(fi->backing_inode); +#endif + return 0; } =20 +struct inode *fuse_iget_backing(struct super_block *sb, + struct inode *backing_inode) +{ + struct inode *inode; + struct fuse_inode *fi; + struct fuse_conn *fc =3D get_fuse_conn_super(sb); + struct fuse_inode_identifier fii =3D { + .nodeid =3D 0, + .backing_inode =3D backing_inode, + }; + struct fuse_attr attr; + unsigned long hash =3D (unsigned long) backing_inode; + + fuse_fill_attr_from_inode(&attr, get_fuse_inode(backing_inode)); + inode =3D iget5_locked(sb, hash, fuse_inode_backing_eq, + fuse_inode_backing_dir_set, &fii); + if (!inode) + return NULL; + + if ((inode->i_state & I_NEW)) { + inode->i_flags |=3D S_NOATIME; + if (!fc->writeback_cache) + inode->i_flags |=3D S_NOCMTIME; + fuse_init_common(inode); + unlock_new_inode(inode); + } + + fi =3D get_fuse_inode(inode); + fuse_init_inode(inode, &attr, fc); + spin_lock(&fi->lock); + fi->nlookup++; + spin_unlock(&fi->lock); + + return inode; +} + struct inode *fuse_iget(struct super_block *sb, u64 nodeid, int generation, struct fuse_attr *attr, u64 attr_valid, u64 attr_version, @@ -467,6 +550,9 @@ struct inode *fuse_iget(struct super_block *sb, u64 nod= eid, struct inode *inode; struct fuse_inode *fi; struct fuse_conn *fc =3D get_fuse_conn_super(sb); + struct fuse_inode_identifier fii =3D { + .nodeid =3D nodeid, + }; =20 /* * Auto mount points get their node id from the submount root, which is @@ -498,7 +584,7 @@ struct inode *fuse_iget(struct super_block *sb, u64 nod= eid, } =20 retry: - inode =3D iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &nodeid= ); + inode =3D iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &fii); if (!inode) return NULL; =20 @@ -533,13 +619,16 @@ struct inode *fuse_ilookup(struct fuse_conn *fc, u64 = nodeid, { struct fuse_mount *fm_iter; struct inode *inode; + struct fuse_inode_identifier fii =3D { + .nodeid =3D nodeid, + }; =20 WARN_ON(!rwsem_is_locked(&fc->killsb)); list_for_each_entry(fm_iter, &fc->mounts, fc_entry) { if (!fm_iter->sb) continue; =20 - inode =3D ilookup5(fm_iter->sb, nodeid, fuse_inode_eq, &nodeid); + inode =3D ilookup5(fm_iter->sb, nodeid, fuse_inode_eq, &fii); if (inode) { if (fm) *fm =3D fm_iter; @@ -1072,7 +1161,7 @@ static struct dentry *fuse_get_dentry(struct super_bl= ock *sb, goto out_err; =20 err =3D fuse_lookup_name(sb, handle->nodeid, &name, &outarg, - &inode); + NULL, &inode); if (err && err !=3D -ENOENT) goto out_err; if (err || !inode) { @@ -1173,7 +1262,7 @@ static struct dentry *fuse_get_parent(struct dentry *= child) return ERR_PTR(-ESTALE); =20 err =3D fuse_lookup_name(child_inode->i_sb, get_node_id(child_inode), - &dotdot_name, &outarg, &inode); + &dotdot_name, &outarg, NULL, &inode); if (err) { if (err =3D=3D -ENOENT) return ERR_PTR(-ESTALE); @@ -1201,6 +1290,9 @@ static const struct export_operations fuse_export_ope= rations =3D { =20 static const struct super_operations fuse_super_operations =3D { .alloc_inode =3D fuse_alloc_inode, +#ifdef CONFIG_FUSE_PASSTHROUGH_DIR + .destroy_inode =3D fuse_destroy_inode, +#endif .free_inode =3D fuse_free_inode, .evict_inode =3D fuse_evict_inode, .write_inode =3D fuse_write_inode, diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index 5ec43ecbceb7..f1bd8e7734ec 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -690,6 +690,10 @@ struct fuse_entry_out { struct fuse_attr attr; }; =20 +struct fuse_entry_backing_out { + uint64_t backing_fd; +}; + struct fuse_forget_in { uint64_t nlookup; }; --=20 2.49.0.1112.g889b7c5bd8-goog From nobody Thu Oct 9 10:42:44 2025 Received: from mail-pj1-f73.google.com (mail-pj1-f73.google.com [209.85.216.73]) (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 C37ED2949F5 for ; Tue, 17 Jun 2025 22:15:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.73 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750198525; cv=none; b=oKF/DACNtKp28b8UzOuqAvLbSETlWMhzuNIBHt63dY9lC1jTghcIcfgAOyFSzKSG0FM4rfTeKoZ2uiIXDMUNww2vxu1ri1XE2UBW/0RuyOabBltspOs70mw0ciovgHxS2SD3A0TWne1OxfW05ubwfmFCIZtq5Qhm4sHAgnpsmdY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1750198525; c=relaxed/simple; bh=FFWNTKvzri1cH3Wbyv4g1IAG9PgKcFE9SJcaiySPcKw=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=r8F4ybzjR9nS0POv921IEJL1GjFcKXkLvbO0pCpuLlWJnC+EYVxZlY7sKIL4nfvxCV/zd3NaVOmjsXRPsYl+R9sWpbZpWS9mIFdcmFOpVYfECzBipNN3QQu6j3xsMr8WwAMFI1IzYJ9wrDU+bjw3JIIu1tz3jqTYlNcaZydfiIQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--paullawrence.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=QyX9T05P; arc=none smtp.client-ip=209.85.216.73 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--paullawrence.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="QyX9T05P" Received: by mail-pj1-f73.google.com with SMTP id 98e67ed59e1d1-3141a9a6888so2365554a91.3 for ; Tue, 17 Jun 2025 15:15:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1750198523; x=1750803323; 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=YiiJDii3e/1E9h1xyTOh07ocmo2tEpRDvB1I6e8ccIw=; b=QyX9T05P1ize9qZoDTkRVv3vbnlh4wRw7NHXLJ64WzJGrzjp/Q3w/bPQsmlY+AU73u Smb4Xcos3/m5UALbJS2I/KZ7clizP5EGQXzuzBcMciMoIwFuJsW2JO4t+DvHFFtBAqOf 3/LB/IrSonZGUQlfH52L+rtSnxU5Vb/qC4XXuoVExYlCDevn4r4PxsrdJzSiNKUxgdvo 5IQcbTSunvJZRPDOZp9RR4LFcxHIaq2Mtbpx3cKJ3QJVB4ygxEDY7vr4XrW4VmU6rJPw TnZHszF3S4rVhKFnoRzl+aCnlkCNwetHC/TEBv/9CjaZIbZUc8G9rNiBfsociQpKcD/0 SjVA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1750198523; x=1750803323; 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=YiiJDii3e/1E9h1xyTOh07ocmo2tEpRDvB1I6e8ccIw=; b=J2QNu9L3Tw8rSlr2GF+JD76KGBmxZairnUw8Cddh/deqTvh+0DXyGWQi2dVlBchHJI EIuBHJwSlrCU+2+GgO7SJasvi4uvurchWzoMLxvdls0vlEprpdDqrHSxk6tPKrRJFpqY 0OGmA2WHSOgtKaP8NU8G5zrTx5FKwED0HMPsX6+vWcqjMHwMf8qL7Jm/lxM1Z5/oyjw9 YZzip50P16tlDVFZvJGI5PIRkZ6mu21NLPS8uz9t+TRKhwVshiGFe9KYXUtMlYjqhmup RzR/23L/SYkv/WNxkTWxG1l6bR6HwyQD080JAx0cgfyxepol9oBELvOIfK9JxSZAF2aP 4WJA== X-Gm-Message-State: AOJu0Yy6iS5+/EEoT4mdALdYP3oXLQH8pbhZXnqtIVZq2po9TnQCKV5X gdX/kmLPYpTwhPgA41oC3jQ2Q3mQNgDPIX1QiUzvGUg/AyEipEUFob4Iwuhmk3FyCDQQyH2bAcO ZXhQgqGm1xp+qSMAI1e14opFUu5JwpA== X-Google-Smtp-Source: AGHT+IEixQwwCtMPZRMitwflJn+MCNzNcpkE0nQ6bs8Ogs5i3eXyrNXuFEV7NYlLJ90/9v4833OID4nJ/hWxw3RrBEI= X-Received: from pjxx16.prod.google.com ([2002:a17:90b:58d0:b0:312:2b3:7143]) (user=paullawrence job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90b:1805:b0:311:c5d9:2c7c with SMTP id 98e67ed59e1d1-313f1daa6b2mr21592430a91.23.1750198523160; Tue, 17 Jun 2025 15:15:23 -0700 (PDT) Date: Tue, 17 Jun 2025 15:14:54 -0700 In-Reply-To: <20250617221456.888231-1-paullawrence@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250617221456.888231-1-paullawrence@google.com> X-Mailer: git-send-email 2.50.0.rc2.696.g1fc2a0284f-goog Message-ID: <20250617221456.888231-3-paullawrence@google.com> Subject: [PATCH v1 2/2] fuse: open/close backing file From: Paul Lawrence To: Miklos Szeredi Cc: linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Paul Lawrence Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add support for opening and closing the backing file on a passthrough directory. * Add backing file to fuse file * Add fuse_inode_has_backing - this is how we will detect whether to use traditional fuse operations or the added backing file operations * Add backing operations to open, flush and close Signed-off-by: Paul Lawrence --- fs/fuse/backing.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dir.c | 16 ----------- fs/fuse/file.c | 34 ++++++++++++++++-------- fs/fuse/fuse_i.h | 39 +++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 27 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 1dcc617bf660..04265bd06695 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -6,6 +6,64 @@ =20 #include "fuse_i.h" =20 +#include + +int fuse_open_backing(struct inode *inode, struct file *file, bool isdir) +{ + struct fuse_mount *fm =3D get_fuse_mount(inode); + struct fuse_file *ff; + int retval; + int mask; + union fuse_dentry *fd =3D get_fuse_dentry(file->f_path.dentry); + struct file *backing_file; + uint32_t flags =3D file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY); + + ff =3D fuse_file_alloc(fm, true); + if (!ff) + return -ENOMEM; + + switch (flags & O_ACCMODE) { + case O_RDONLY: + mask =3D MAY_READ; + break; + + case O_WRONLY: + mask =3D MAY_WRITE; + break; + + case O_RDWR: + mask =3D MAY_READ | MAY_WRITE; + break; + + default: + retval =3D -EINVAL; + goto outerr; + } + + retval =3D inode_permission(&nop_mnt_idmap, + get_fuse_inode(inode)->backing_inode, mask); + if (retval) + goto outerr; + + backing_file =3D backing_file_open(&file->f_path, file->f_flags, + &fd->backing_path, current_cred()); + + if (IS_ERR(backing_file)) { + retval =3D PTR_ERR(backing_file); + goto outerr; + } + + ff->backing_file =3D backing_file; + ff->nodeid =3D get_fuse_inode(inode)->nodeid; + file->private_data =3D ff; + return 0; + +outerr: + if (retval) + fuse_file_free(ff); + return retval; +} + int fuse_handle_backing(struct fuse_entry_backing *feb, struct inode **backing_inode, struct path *backing_path) { @@ -27,3 +85,13 @@ int fuse_handle_backing(struct fuse_entry_backing *feb, =20 return 0; } + +int fuse_flush_backing(struct file *file, fl_owner_t id) +{ + struct fuse_file *fuse_file =3D file->private_data; + struct file *backing_file =3D fuse_file->backing_file; + + if (backing_file->f_op->flush) + return backing_file->f_op->flush(backing_file, id); + return 0; +} diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 909463fae94d..658898f324b5 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -44,18 +44,7 @@ static inline u64 fuse_dentry_time(const struct dentry *= entry) { return (u64)entry->d_fsdata; } - #else -union fuse_dentry { - struct { - u64 time; -#ifdef CONFIG_FUSE_PASSTHROUGH_DIR - struct path backing_path; -#endif - }; - struct rcu_head rcu; -}; - static inline void __fuse_dentry_settime(struct dentry *dentry, u64 time) { ((union fuse_dentry *) dentry->d_fsdata)->time =3D time; @@ -65,11 +54,6 @@ static inline u64 fuse_dentry_time(const struct dentry *= entry) { return ((union fuse_dentry *) entry->d_fsdata)->time; } - -static inline union fuse_dentry *get_fuse_dentry(const struct dentry *entr= y) -{ - return entry->d_fsdata; -} #endif =20 static void fuse_dentry_settime(struct dentry *dentry, u64 time) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 754378dd9f71..0cd0a94073c7 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -21,6 +21,7 @@ #include #include #include +#include =20 static int fuse_send_open(struct fuse_mount *fm, u64 nodeid, unsigned int open_flags, int opcode, @@ -105,19 +106,24 @@ static void fuse_file_put(struct fuse_file *ff, bool = sync) struct fuse_release_args *ra =3D &ff->args->release_args; struct fuse_args *args =3D (ra ? &ra->args : NULL); =20 - if (ra && ra->inode) - fuse_file_io_release(ff, ra->inode); - - if (!args) { - /* Do nothing when server does not implement 'open' */ - } else if (sync) { - fuse_simple_request(ff->fm, args); + if (ff->backing_file) { fuse_release_end(ff->fm, args, 0); + fput(ff->backing_file); } else { - args->end =3D fuse_release_end; - if (fuse_simple_background(ff->fm, args, - GFP_KERNEL | __GFP_NOFAIL)) - fuse_release_end(ff->fm, args, -ENOTCONN); + if (ra && ra->inode) + fuse_file_io_release(ff, ra->inode); + + if (!args) { + /* Do nothing when server does not implement 'open' */ + } else if (sync) { + fuse_simple_request(ff->fm, args); + fuse_release_end(ff->fm, args, 0); + } else { + args->end =3D fuse_release_end; + if (fuse_simple_background(ff->fm, args, + GFP_KERNEL | __GFP_NOFAIL)) + fuse_release_end(ff->fm, args, -ENOTCONN); + } } kfree(ff); } @@ -248,6 +254,9 @@ static int fuse_open(struct inode *inode, struct file *= file) if (err) return err; =20 + if (fuse_inode_has_backing(inode)) + return fuse_open_backing(inode, file, false); + if (is_wb_truncate || dax_truncate) inode_lock(inode); =20 @@ -522,6 +531,9 @@ static int fuse_flush(struct file *file, fl_owner_t id) FUSE_ARGS(args); int err; =20 + if (fuse_inode_has_backing(file->f_inode)) + return fuse_flush_backing(file, id); + if (fuse_is_bad(inode)) return -EIO; =20 diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 1dc04bc6ac49..a27c05810bab 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -96,6 +96,25 @@ struct fuse_submount_lookup { struct fuse_forget_link *forget; }; =20 + +/** FUSE specific dentry data */ +#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_PASSTHROUGH_DIR) +union fuse_dentry { + struct { + u64 time; +#ifdef CONFIG_FUSE_PASSTHROUGH_DIR + struct path backing_path; +#endif + }; + struct rcu_head rcu; +}; + +static inline union fuse_dentry *get_fuse_dentry(const struct dentry *entr= y) +{ + return entry->d_fsdata; +} +#endif + /** Container for data related to mapping to backing file */ struct fuse_backing { struct file *file; @@ -287,6 +306,14 @@ struct fuse_file { =20 } readdir; =20 +#ifdef CONFIG_FUSE_PASSTHROUGH_DIR + /** + * TODO: Reconcile with passthrough file + * backing file when in bpf mode + */ + struct file *backing_file; +#endif + /** RB node to be linked on fuse_conn->polled_files */ struct rb_node polled_node; =20 @@ -1597,4 +1624,16 @@ extern void fuse_sysctl_unregister(void); /* backing.c */ int fuse_handle_backing(struct fuse_entry_backing *feb, struct inode **backing_inode, struct path *backing_path); + + +static inline bool fuse_inode_has_backing(struct inode *inode) +{ + struct fuse_inode *fuse_inode =3D get_fuse_inode(inode); + + return fuse_inode && fuse_inode->backing_inode; +} + +int fuse_open_backing(struct inode *inode, struct file *file, bool isdir); +int fuse_flush_backing(struct file *file, fl_owner_t id); + #endif /* _FS_FUSE_I_H */ --=20 2.49.0.1112.g889b7c5bd8-goog