From nobody Tue Apr 7 12:20:30 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5E421393DF4; Wed, 25 Feb 2026 11:25:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772018721; cv=none; b=JiaX+CzhmXaNdc7LJj3hwEXCGH0P0DAHUYlxXc8OALPE5GrTfbVoyWAWkgth+vPClUhTde30hm3IbMf2MNoFHF6HBqCfrQEeoH/pLXmX4vBWOLN4MJGhJ7JpMUD8Utz+O2owV6jEpwhXxIJC8zhW1oHGrTwFZt0u3eZLOXMpwl0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772018721; c=relaxed/simple; bh=LXipgu/gi1Nc+PULiJhx0dVsXZWHHNgh0npXMeV5JhI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=j860f3nuTI2qSe0papA92pxd75WWcaYMOy2V38NaCJhOplyUcKoE3Yj4FqOto0B2DbOwrBsdNvgt7xae+bkBxZiSSePWrusovch/DH5sZ60gh+1UdTuLKMOA5rtFPW6ZURmvVPxLQ7yrJvB4DQcZrTropivCrU5X+jVsVWn8mIs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=B3n9gPOS; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="B3n9gPOS" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=saZDrpINJVghEJy5/rEMg4b8oT1k8DDbtqGwK8JORVI=; b=B3n9gPOSWnZvre2b+n1POsGQf7 UgvcZLGgApRdg9eLKYzC9ujYCc6OvVAEurm1NDdjjNVJk+/3OJcnOVSz2ksHYP7qpqxazkZT1KLDV bOLOI3R3W6f2Esdk/gC/I4ac7a1XIOvyo6ijoHGvEc3Tc6jFXlh5ubIyI1SQGn+PuIck4b6eJUrQe wKQIBN+6IYlichc3b4LsNhcn7CygoMtf4g/VatkfQnkutoccGWbqHIQyIp6rTJGS39qFbFDNx2Gme BIRjmlpklNpMJXYkkFZ69UaqarWNynZmzpGeXitkTAt9ViazegmyfoLNSjn1rk+8ZVmfoRyDv5jtq EFKqgtVA==; Received: from bl17-145-117.dsl.telepac.pt ([188.82.145.117] helo=localhost) by fanzine2.igalia.com with utf8esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1vvD0m-005CmE-MC; Wed, 25 Feb 2026 12:25:00 +0100 From: Luis Henriques To: Miklos Szeredi , Amir Goldstein , Bernd Schubert , Bernd Schubert , "Darrick J. Wong" , Horst Birthelmer , Joanne Koong , Kevin Chen Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Matt Harvey , kernel-dev@igalia.com, Luis Henriques Subject: [RFC PATCH v3 1/8] fuse: simplify fuse_lookup_name() interface Date: Wed, 25 Feb 2026 11:24:32 +0000 Message-ID: <20260225112439.27276-2-luis@igalia.com> In-Reply-To: <20260225112439.27276-1-luis@igalia.com> References: <20260225112439.27276-1-luis@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" fuse_lookup_name() requires a struct fuse_entry_out to be passed in. However, the only caller that really needs it is fuse_lookup(). And even this function only cares about the dentry time. This patch simplifies fuse_lookup_name() so that it doesn't require a struct as a parameter, replacing it by a local variable. Instead, there'll be an (optional) out argument, that can be used to return the dentry time. Signed-off-by: Luis Henriques --- fs/fuse/dir.c | 36 +++++++++++++++++++----------------- fs/fuse/fuse_i.h | 2 +- fs/fuse/inode.c | 7 ++----- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index ef297b867060..e3000affff88 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -548,10 +548,11 @@ 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) + u64 *time, struct inode **inode) { struct fuse_mount *fm =3D get_fuse_mount_super(sb); FUSE_ARGS(args); + struct fuse_entry_out outarg; struct fuse_forget_link *forget; u64 attr_version, evict_ctr; int err; @@ -570,30 +571,34 @@ 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); err =3D fuse_simple_request(fm, &args); /* Zero nodeid is same as -ENOENT, but with valid timeout */ - if (err || !outarg->nodeid) + if (err || !outarg.nodeid) goto out_put_forget; =20 err =3D -EIO; - if (fuse_invalid_attr(&outarg->attr)) + if (fuse_invalid_attr(&outarg.attr)) goto out_put_forget; - if (outarg->nodeid =3D=3D FUSE_ROOT_ID && outarg->generation !=3D 0) { + 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; + outarg.generation =3D 0; } =20 - *inode =3D fuse_iget(sb, outarg->nodeid, outarg->generation, - &outarg->attr, ATTR_TIMEOUT(outarg), + *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); + fuse_queue_forget(fm->fc, forget, outarg.nodeid, 1); goto out; } err =3D 0; =20 + if (time) + *time =3D fuse_time_to_jiffies(outarg.entry_valid, + outarg.entry_valid_nsec); + out_put_forget: kfree(forget); out: @@ -603,12 +608,11 @@ int fuse_lookup_name(struct super_block *sb, u64 node= id, const struct qstr *name static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, unsigned int flags) { - struct fuse_entry_out outarg; struct fuse_conn *fc; struct inode *inode; struct dentry *newent; + u64 time =3D 0; int err, epoch; - bool outarg_valid =3D true; bool locked; =20 if (fuse_is_bad(dir)) @@ -619,12 +623,10 @@ static struct dentry *fuse_lookup(struct inode *dir, = struct 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); + &time, &inode); fuse_unlock_inode(dir, locked); - if (err =3D=3D -ENOENT) { - outarg_valid =3D false; + if (err =3D=3D -ENOENT) err =3D 0; - } if (err) goto out_err; =20 @@ -639,8 +641,8 @@ static struct dentry *fuse_lookup(struct inode *dir, st= ruct dentry *entry, =20 entry =3D newent ? newent : entry; entry->d_time =3D epoch; - if (outarg_valid) - fuse_change_entry_timeout(entry, &outarg); + if (time) + fuse_dentry_settime(entry, time); else fuse_invalidate_entry_cache(entry); =20 diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 3184ef864cf0..6178a012f36c 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1149,7 +1149,7 @@ struct inode *fuse_iget(struct super_block *sb, u64 n= odeid, 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); + u64 *time, struct inode **inode); =20 /** * Send FORGET command diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 38ca362ee2ca..8231c207abea 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1089,14 +1089,12 @@ static struct dentry *fuse_get_dentry(struct super_= block *sb, =20 inode =3D ilookup5(sb, handle->nodeid, fuse_inode_eq, &handle->nodeid); if (!inode) { - struct fuse_entry_out outarg; const struct qstr name =3D QSTR_INIT(".", 1); =20 if (!fc->export_support) goto out_err; =20 - err =3D fuse_lookup_name(sb, handle->nodeid, &name, &outarg, - &inode); + err =3D fuse_lookup_name(sb, handle->nodeid, &name, NULL, &inode); if (err && err !=3D -ENOENT) goto out_err; if (err || !inode) { @@ -1190,14 +1188,13 @@ static struct dentry *fuse_get_parent(struct dentry= *child) struct fuse_conn *fc =3D get_fuse_conn(child_inode); struct inode *inode; struct dentry *parent; - struct fuse_entry_out outarg; int err; =20 if (!fc->export_support) return ERR_PTR(-ESTALE); =20 err =3D fuse_lookup_name(child_inode->i_sb, get_node_id(child_inode), - &dotdot_name, &outarg, &inode); + &dotdot_name, NULL, &inode); if (err) { if (err =3D=3D -ENOENT) return ERR_PTR(-ESTALE); From nobody Tue Apr 7 12:20:30 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 075593939CE; Wed, 25 Feb 2026 11:25:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772018720; cv=none; b=HbjG6saOy693KCpsgmqxx9pJ4Rzl4b2Yn9GFIfDLwXRUsWowMSyeIM/G24hAv3NBlKi8tGJWNoS+4p2V5HoVn/y5hBXeTJixByEI+05GoRRqxPw8mCant/tJQcPWlO9Zl87jO5HlBRJJqErXUPc8obCWPJ6YQGJstJ22UV/OZHY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772018720; c=relaxed/simple; bh=f6lcVUK6pgrWtIVFsTWEzx5AVlThjPuX4q1NOOOOg4g=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ITnqsDINGdQy+bcTUZ8XhtkFV97vJhRPhWw4GSmYjtNcqbjR4ozNl5JbmEtUoq62ZZTPDDCfGqUh2PCTp+NASEUmgRgRTJppZEGjN/5G1uYEtmd4Bw7IVkOlBBcgsTsclsagftoGdya/SrlBLm/lWsjgYfiuoAFsOql3OrT9wWw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=W5yfc0cY; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="W5yfc0cY" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=UKvw97aLLhmYWeg2HbSDZr64p/f1GKlxJKIAPRD8xWg=; b=W5yfc0cYXoqCoYjR52vwRHGVja 1sBSAfc4HhOmYQ17Q/FqAhszuyzXV6p7J9qbUwDEsZTVPCQOjz3YVeYKM/HTvtYrEA8pWMR0pJ5Il no9uLcQAnK86aqz/zePcMgIV9MWuBJSDpdBUc5WdixhS2rTLXGhoPe1uihhMEXvMy/W7B7AlrSKUQ nIRukiiVGaqbok0XjaD15JNxFsKOCxMq9gxFM5/dpo6dhVzh6VA9af01Wr+R5pF4noY5iAslDSZQK ccNFNzeBqw2UZzojzOxe6L+SZpPCmHQ7mDfysICMeCS25RxkFvhNAVrhE9eVecKiwsPrE+Coi9eyG fG0OW6gw==; Received: from bl17-145-117.dsl.telepac.pt ([188.82.145.117] helo=localhost) by fanzine2.igalia.com with utf8esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1vvD0n-005CmH-D6; Wed, 25 Feb 2026 12:25:01 +0100 From: Luis Henriques To: Miklos Szeredi , Amir Goldstein , Bernd Schubert , Bernd Schubert , "Darrick J. Wong" , Horst Birthelmer , Joanne Koong , Kevin Chen Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Matt Harvey , kernel-dev@igalia.com, Luis Henriques Subject: [RFC PATCH v3 2/8] fuse: export extend_arg() and factor out fuse_ext_size() Date: Wed, 25 Feb 2026 11:24:33 +0000 Message-ID: <20260225112439.27276-3-luis@igalia.com> In-Reply-To: <20260225112439.27276-1-luis@igalia.com> References: <20260225112439.27276-1-luis@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Export (and rename) extend_arg() and fuse_ext_size() as these functions are useful for using extension headers in other places. Signed-off-by: Luis Henriques --- fs/fuse/dir.c | 9 ++------- fs/fuse/fuse_i.h | 7 +++++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index e3000affff88..f5eacea44896 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -713,7 +713,7 @@ static int get_security_context(struct dentry *entry, u= mode_t mode, return err; } =20 -static void *extend_arg(struct fuse_in_arg *buf, u32 bytes) +void *fuse_extend_arg(struct fuse_in_arg *buf, u32 bytes) { void *p; u32 newlen =3D buf->size + bytes; @@ -733,11 +733,6 @@ static void *extend_arg(struct fuse_in_arg *buf, u32 b= ytes) return p + newlen - bytes; } =20 -static u32 fuse_ext_size(size_t size) -{ - return FUSE_REC_ALIGN(sizeof(struct fuse_ext_header) + size); -} - /* * This adds just a single supplementary group that matches the parent's g= roup. */ @@ -758,7 +753,7 @@ static int get_create_supp_group(struct mnt_idmap *idma= p, !vfsgid_in_group_p(vfsgid)) return 0; =20 - xh =3D extend_arg(ext, sg_len); + xh =3D fuse_extend_arg(ext, sg_len); if (!xh) return -ENOMEM; =20 diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 6178a012f36c..135027efec7a 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1410,6 +1410,13 @@ int fuse_valid_type(int m); =20 bool fuse_invalid_attr(struct fuse_attr *attr); =20 +void *fuse_extend_arg(struct fuse_in_arg *buf, u32 bytes); + +static inline u32 fuse_ext_size(size_t size) +{ + return FUSE_REC_ALIGN(sizeof(struct fuse_ext_header) + size); +} + /** * Is current process allowed to perform filesystem operation? */ From nobody Tue Apr 7 12:20:30 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 07614393DCD; Wed, 25 Feb 2026 11:25:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772018721; cv=none; b=ReQOQtYapwZS8JkDlRHRZj536DbBJBgrvAYcG/aWXV3bAqfSuP0QzZiHpm6N2hJmOwhuzl3geqfbx8yCiNcY3ySCorV06y0PEAtcXfcNekitL47b0j796BQo4y1pJ7+qTTHIQzpjekD3ZU/RKuVOV48ZMWwWP1stdlZUPGuMgEY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772018721; c=relaxed/simple; bh=dzyiJxL+0IciaVcFaUcwBHiLG2oPDsxQJvk/FQi1UrI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=XXU/grLhNWWUMGcVxFp5YxK8VyZdl7G70FWqp5zs4+cw3vUdhb8SE3e+3AQWqvIFFIimg93SaW0gN0/f36fHECSTWq4Gl3AX8coxxJrufoCGPSjqpjS12raxTRGllndiTohbIYAt/eVPI9mWHXomMcbF83KOzpADmZLSVMIyAoE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=byGkzESl; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="byGkzESl" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=+T9xNGkQlR6pAOOmLdTi86TC/esKS7VSnEOEMfQaXK0=; b=byGkzESlXYxD28tyleHXrVy22D 02qPXjLFNhZjn5R64Dk1kBHXJZLhSsqOYI9p7GiPAyJq5mWO8KayVZM4rsupfsVJRsVUrO0xapPaM 6Kt77SYD93d+mcgg5q+/2uVYE3jstEfCEtXHgYr1Zlb5Y030LBGo/YZoGONbLw+CIRCn5RyId0tDw m3OXrsUlB8qzUS/ZcV9F0DInFyE+lOM0fkUvlcG3c4Wiwk24N2bdQEP0Q/xTpVU+S/fJeiYGBPxUq 7bOpsvc7Q4Z+j+Zufa5YMtEzh958LqEz5JuSiyT90evyvfEs1jj/gEngJiUq9/Ky6bPMKFsK37BuO w3NET8qA==; Received: from bl17-145-117.dsl.telepac.pt ([188.82.145.117] helo=localhost) by fanzine2.igalia.com with utf8esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1vvD0o-005CmM-1u; Wed, 25 Feb 2026 12:25:02 +0100 From: Luis Henriques To: Miklos Szeredi , Amir Goldstein , Bernd Schubert , Bernd Schubert , "Darrick J. Wong" , Horst Birthelmer , Joanne Koong , Kevin Chen Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Matt Harvey , kernel-dev@igalia.com, Luis Henriques Subject: [RFC PATCH v3 3/8] fuse: store index of the variable length argument Date: Wed, 25 Feb 2026 11:24:34 +0000 Message-ID: <20260225112439.27276-4-luis@igalia.com> In-Reply-To: <20260225112439.27276-1-luis@igalia.com> References: <20260225112439.27276-1-luis@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Operations that have a variable length argument assume that it will always be the last argument on the list. This patch allows this assumption to be removed by keeping track of the index of variable length argument. Signed-off-by: Luis Henriques --- fs/fuse/compound.c | 1 + fs/fuse/cuse.c | 1 + fs/fuse/dev.c | 4 ++-- fs/fuse/dir.c | 1 + fs/fuse/file.c | 1 + fs/fuse/fuse_i.h | 2 ++ fs/fuse/inode.c | 1 + fs/fuse/ioctl.c | 1 + fs/fuse/virtio_fs.c | 6 +++--- fs/fuse/xattr.c | 2 ++ 10 files changed, 15 insertions(+), 5 deletions(-) diff --git a/fs/fuse/compound.c b/fs/fuse/compound.c index 1a85209f4e99..2dc024301aad 100644 --- a/fs/fuse/compound.c +++ b/fs/fuse/compound.c @@ -153,6 +153,7 @@ ssize_t fuse_compound_send(struct fuse_compound_req *co= mpound) .in_numargs =3D 2, .out_numargs =3D 2, .out_argvar =3D true, + .out_argvar_idx =3D 1, }; unsigned int req_count =3D compound->compound_header.count; size_t total_expected_out_size =3D 0; diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c index dfcb98a654d8..3ce8ee9a4275 100644 --- a/fs/fuse/cuse.c +++ b/fs/fuse/cuse.c @@ -460,6 +460,7 @@ static int cuse_send_init(struct cuse_conn *cc) ap->args.out_args[0].value =3D &ia->out; ap->args.out_args[1].size =3D CUSE_INIT_INFO_MAX; ap->args.out_argvar =3D true; + ap->args.out_argvar_idx =3D 1; ap->args.out_pages =3D true; ap->num_folios =3D 1; ap->folios =3D &ia->folio; diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 0b0241f47170..5b02724f4377 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -694,7 +694,7 @@ ssize_t __fuse_simple_request(struct mnt_idmap *idmap, ret =3D req->out.h.error; if (!ret && args->out_argvar) { BUG_ON(args->out_numargs =3D=3D 0); - ret =3D args->out_args[args->out_numargs - 1].size; + ret =3D args->out_args[args->out_argvar_idx].size; } fuse_put_request(req); =20 @@ -2157,7 +2157,7 @@ int fuse_copy_out_args(struct fuse_copy_state *cs, st= ruct fuse_args *args, if (reqsize < nbytes || (reqsize > nbytes && !args->out_argvar)) return -EINVAL; else if (reqsize > nbytes) { - struct fuse_arg *lastarg =3D &args->out_args[args->out_numargs-1]; + struct fuse_arg *lastarg =3D &args->out_args[args->out_argvar_idx]; unsigned diffsize =3D reqsize - nbytes; =20 if (diffsize > lastarg->size) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index f5eacea44896..a1121feb63ee 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1835,6 +1835,7 @@ static int fuse_readlink_folio(struct inode *inode, s= truct folio *folio) ap.args.nodeid =3D get_node_id(inode); ap.args.out_pages =3D true; ap.args.out_argvar =3D true; + ap.args.out_argvar_idx =3D 0; ap.args.page_zeroing =3D true; ap.args.out_numargs =3D 1; ap.args.out_args[0].size =3D desc.length; diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 49c21498230d..1045d74dd95f 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -682,6 +682,7 @@ void fuse_read_args_fill(struct fuse_io_args *ia, struc= t file *file, loff_t pos, args->in_args[0].size =3D sizeof(ia->read.in); args->in_args[0].value =3D &ia->read.in; args->out_argvar =3D true; + args->out_argvar_idx =3D 0; args->out_numargs =3D 1; args->out_args[0].size =3D count; } diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 135027efec7a..04f09e2ccfd0 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -331,6 +331,8 @@ struct fuse_args { uint32_t opcode; uint8_t in_numargs; uint8_t out_numargs; + /* The index of the variable length out arg */ + uint8_t out_argvar_idx; uint8_t ext_idx; bool force:1; bool noreply:1; diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 8231c207abea..006436a3ad06 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1540,6 +1540,7 @@ static struct fuse_init_args *fuse_new_init(struct fu= se_mount *fm) with interface version < 7.5. Rest of init_out is zeroed by do_get_request(), so a short reply is not a problem */ ia->args.out_argvar =3D true; + ia->args.out_argvar_idx =3D 0; ia->args.out_args[0].size =3D sizeof(ia->out); ia->args.out_args[0].value =3D &ia->out; ia->args.force =3D true; diff --git a/fs/fuse/ioctl.c b/fs/fuse/ioctl.c index 07a02e47b2c3..7eb8d7a59edc 100644 --- a/fs/fuse/ioctl.c +++ b/fs/fuse/ioctl.c @@ -337,6 +337,7 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd,= unsigned long arg, ap.args.out_args[1].size =3D out_size; ap.args.out_pages =3D true; ap.args.out_argvar =3D true; + ap.args.out_argvar_idx =3D 1; =20 transferred =3D fuse_send_ioctl(fm, &ap.args, &outarg); err =3D transferred; diff --git a/fs/fuse/virtio_fs.c b/fs/fuse/virtio_fs.c index 057e65b51b99..dd681bc672b8 100644 --- a/fs/fuse/virtio_fs.c +++ b/fs/fuse/virtio_fs.c @@ -738,7 +738,7 @@ static void copy_args_from_argbuf(struct fuse_args *arg= s, struct fuse_req *req) unsigned int argsize =3D args->out_args[i].size; =20 if (args->out_argvar && - i =3D=3D args->out_numargs - 1 && + i =3D=3D args->out_argvar_idx && argsize > remaining) { argsize =3D remaining; } @@ -746,13 +746,13 @@ static void copy_args_from_argbuf(struct fuse_args *a= rgs, struct fuse_req *req) memcpy(args->out_args[i].value, req->argbuf + offset, argsize); offset +=3D argsize; =20 - if (i !=3D args->out_numargs - 1) + if (i !=3D args->out_argvar_idx) remaining -=3D argsize; } =20 /* Store the actual size of the variable-length arg */ if (args->out_argvar) - args->out_args[args->out_numargs - 1].size =3D remaining; + args->out_args[args->out_argvar_idx].size =3D remaining; =20 kfree(req->argbuf); req->argbuf =3D NULL; diff --git a/fs/fuse/xattr.c b/fs/fuse/xattr.c index 93dfb06b6cea..f123446fe537 100644 --- a/fs/fuse/xattr.c +++ b/fs/fuse/xattr.c @@ -73,6 +73,7 @@ ssize_t fuse_getxattr(struct inode *inode, const char *na= me, void *value, args.out_numargs =3D 1; if (size) { args.out_argvar =3D true; + args.out_argvar_idx =3D 0; args.out_args[0].size =3D size; args.out_args[0].value =3D value; } else { @@ -135,6 +136,7 @@ ssize_t fuse_listxattr(struct dentry *entry, char *list= , size_t size) args.out_numargs =3D 1; if (size) { args.out_argvar =3D true; + args.out_argvar_idx =3D 0; args.out_args[0].size =3D size; args.out_args[0].value =3D list; } else { From nobody Tue Apr 7 12:20:30 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6104D393DF7; Wed, 25 Feb 2026 11:25:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772018721; cv=none; b=CcdwmjKuKZ2LDFOauOJz0bpAy+LDquWn46ZP/VV6et+GfR1BFm8TI/gM3g+NAogQt9BZEh3iS8VAj7SFFrZ9FqOP9TxQt3NpdwkNt5Y1f7ySmM+VfvBt0+hrUO0XmY0N1/lBJG0UAqLvUqefdqUjjUK4jWHcQsBMu2EA9bcQj5E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772018721; c=relaxed/simple; bh=Qh8KwzManAX1Jvn1/cEkLKhWxqyawNeQEFLzMnfIC/w=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=H65VPQGmN8jMvJoq9MvTMuTwIkMXot7jB0jEIW5PE1CCy3vo2MJCzHhySAsNo9f7sRnQkerW/zpq6jQAlr+aUOI+qvQMSMFwbw+Qfzy8gA2CUL0ttM9QLlmoNivjOeOsEX6DAFiORBchcdUbLrIO0CdoqweCbwrY/GkuW2GYhAw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=alfu1oFC; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="alfu1oFC" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=9keiUKuR9YO5TYYSpyLSfn+0/T4EYYqevZQeVZSKLv8=; b=alfu1oFC0u+2Y6KZiVHfKiLN0t FqRk/3uYvP8UY2N5Pqo2gJcW6oUzYC/iv66AqkTr3jD0pDY0gfODtXc65cQvVGYg487N3Gp3/aF0y gtObpKSQeibkBG0KaTR/n3GBcdVQ/cwckJQFyLa31Ez8nWn/R6lhOIzWoV0xx7fgYUQe25gelDsNV AUUsR4nFnIOm8BucPmp9Zt2XU2w/rNi8KWrC7cFzs1oPS+E/lD+SnWeGrQhKWoKWSOXvEytIb2IJI vKEaAWtwiFMWKCt+pI7YXQ32qGELhl5Pn7XFNMS8nUwK1/lowJDqe7U5+fAFtIn5eSHiosoUphMLv Kgp658qQ==; Received: from bl17-145-117.dsl.telepac.pt ([188.82.145.117] helo=localhost) by fanzine2.igalia.com with utf8esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1vvD0o-005CmO-OQ; Wed, 25 Feb 2026 12:25:02 +0100 From: Luis Henriques To: Miklos Szeredi , Amir Goldstein , Bernd Schubert , Bernd Schubert , "Darrick J. Wong" , Horst Birthelmer , Joanne Koong , Kevin Chen Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Matt Harvey , kernel-dev@igalia.com, Luis Henriques Subject: [RFC PATCH v3 4/8] fuse: drop unnecessary argument from fuse_lookup_init() Date: Wed, 25 Feb 2026 11:24:35 +0000 Message-ID: <20260225112439.27276-5-luis@igalia.com> In-Reply-To: <20260225112439.27276-1-luis@igalia.com> References: <20260225112439.27276-1-luis@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Remove the fuse_conn argument from function fuse_lookup_init() as it isn't used since commit 21f621741a77 ("fuse: fix LOOKUP vs INIT compat handling"). Signed-off-by: Luis Henriques --- fs/fuse/dir.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index a1121feb63ee..92c9ebfb4985 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -354,8 +354,8 @@ static void fuse_invalidate_entry(struct dentry *entry) fuse_invalidate_entry_cache(entry); } =20 -static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args, - u64 nodeid, const struct qstr *name, +static void fuse_lookup_init(struct fuse_args *args, u64 nodeid, + const struct qstr *name, struct fuse_entry_out *outarg) { memset(outarg, 0, sizeof(struct fuse_entry_out)); @@ -421,8 +421,7 @@ static int fuse_dentry_revalidate(struct inode *dir, co= nst struct qstr *name, =20 attr_version =3D fuse_get_attr_version(fm->fc); =20 - fuse_lookup_init(fm->fc, &args, get_node_id(dir), - name, &outarg); + fuse_lookup_init(&args, get_node_id(dir), name, &outarg); ret =3D fuse_simple_request(fm, &args); /* Zero nodeid is same as -ENOENT */ if (!ret && !outarg.nodeid) @@ -571,7 +570,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid= , 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(&args, nodeid, name, &outarg); err =3D fuse_simple_request(fm, &args); /* Zero nodeid is same as -ENOENT, but with valid timeout */ if (err || !outarg.nodeid) From nobody Tue Apr 7 12:20:30 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 074BA3939B1; Wed, 25 Feb 2026 11:25:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772018721; cv=none; b=myLd/smQbzAacBb8qHTs8eeOamrglnme5TAQyUpaxNrelhkqG13HVEHXVF3MV+kkUPcGXNWul0SdibKQYQymJX4+tbv0Ab+7upVPl1+TuwPOIg9WS5FAa2w1muORby+688Ri9WcOmQkESvWdUQoTH/iSUWcCvhq79Or1gQu+Aig= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772018721; c=relaxed/simple; bh=d4CAhtgSioguYg2upsew64NwHgPmgU2XvI3EYTdSKr4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=f4C9fl17ac0Fj+ipbhQay30jCG9NtoDrv8N/CHcopHBLOjrnHlORYfKQyLjjOxnPQ8RWMcPJFQCiyVPSznTHD5MaknI5v6Ge1cL5fyZjwUjMgfwE046g1Rdiojv5WV/oXSFXo3vxE4dytl3k7GNxiTrguBg1HxQ4/PXWyFErgMg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=GInl6byK; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="GInl6byK" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=DXtMVtmrxddzuhbM1DC/GuIOIXfI5qn7KhrEBeX/Thc=; b=GInl6byKesFpK516vUUaA3eLQK OEZEqSj05AXyLcHd1iLvYT1siMAxjOG+M0Hnv9CLMiCiCQR0Kkde+pxwbI7GhTVtUs2oeijYR3YHZ gRv+6YiL8TUbce0Kt3WbzHyQG8UYUXfC/iIHUUmiJ90ZgI0ycxD1BShNAip9HPL4x82FOmC25XhJL ovDiYwrQHImNfGeo63Rmi2gXOXmc/EqAf68DZechQb+F9VzUGJSLFCafyPT2S6Jq8pFCJ4qrtWyza 4W/0Hi/1kL+L2frsAErAJ70V+gw5gHW+WZ8z3qyPIfbYQ19/Dd2Z2XV4iXvcV333M1Y8GnOT+Kj8s LAWLdvPA==; Received: from bl17-145-117.dsl.telepac.pt ([188.82.145.117] helo=localhost) by fanzine2.igalia.com with utf8esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1vvD0p-005Cma-IG; Wed, 25 Feb 2026 12:25:03 +0100 From: Luis Henriques To: Miklos Szeredi , Amir Goldstein , Bernd Schubert , Bernd Schubert , "Darrick J. Wong" , Horst Birthelmer , Joanne Koong , Kevin Chen Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Matt Harvey , kernel-dev@igalia.com, Luis Henriques Subject: [RFC PATCH v3 5/8] fuse: extract helper functions from fuse_do_statx() Date: Wed, 25 Feb 2026 11:24:36 +0000 Message-ID: <20260225112439.27276-6-luis@igalia.com> In-Reply-To: <20260225112439.27276-1-luis@igalia.com> References: <20260225112439.27276-1-luis@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Split function fuse_do_statx(), so that we get two helper functions: one to initialise the arguments and another to update the attributes and statistics. This will allow compound operations to re-use these two helper= s. Signed-off-by: Luis Henriques --- fs/fuse/dir.c | 88 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 92c9ebfb4985..5c0f1364c392 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1406,43 +1406,37 @@ static void fuse_statx_to_attr(struct fuse_statx *s= x, struct fuse_attr *attr) attr->blksize =3D sx->blksize; } =20 -static int fuse_do_statx(struct mnt_idmap *idmap, struct inode *inode, - struct file *file, struct kstat *stat) +static void fuse_statx_init(struct fuse_args *args, struct fuse_statx_in *= inarg, + u64 nodeid, struct fuse_file *ff, + struct fuse_statx_out *outarg) { - int err; - struct fuse_attr attr; - struct fuse_statx *sx; - struct fuse_statx_in inarg; - struct fuse_statx_out outarg; - struct fuse_mount *fm =3D get_fuse_mount(inode); - u64 attr_version =3D fuse_get_attr_version(fm->fc); - FUSE_ARGS(args); - - memset(&inarg, 0, sizeof(inarg)); - memset(&outarg, 0, sizeof(outarg)); - /* Directories have separate file-handle space */ - if (file && S_ISREG(inode->i_mode)) { - struct fuse_file *ff =3D file->private_data; + memset(inarg, 0, sizeof(*inarg)); + memset(outarg, 0, sizeof(*outarg)); =20 - inarg.getattr_flags |=3D FUSE_GETATTR_FH; - inarg.fh =3D ff->fh; + if (ff) { + inarg->getattr_flags |=3D FUSE_GETATTR_FH; + inarg->fh =3D ff->fh; } /* For now leave sync hints as the default, request all stats. */ - inarg.sx_flags =3D 0; - inarg.sx_mask =3D STATX_BASIC_STATS | STATX_BTIME; - args.opcode =3D FUSE_STATX; - args.nodeid =3D get_node_id(inode); - args.in_numargs =3D 1; - args.in_args[0].size =3D sizeof(inarg); - args.in_args[0].value =3D &inarg; - args.out_numargs =3D 1; - args.out_args[0].size =3D sizeof(outarg); - args.out_args[0].value =3D &outarg; - err =3D fuse_simple_request(fm, &args); - if (err) - return err; + inarg->sx_flags =3D 0; + inarg->sx_mask =3D STATX_BASIC_STATS | STATX_BTIME; + args->opcode =3D FUSE_STATX; + args->nodeid =3D nodeid; + args->in_numargs =3D 1; + args->in_args[0].size =3D sizeof(*inarg); + args->in_args[0].value =3D inarg; + args->out_numargs =3D 1; + args->out_args[0].size =3D sizeof(*outarg); + args->out_args[0].value =3D outarg; +} + +static int fuse_statx_update(struct mnt_idmap *idmap, + struct fuse_statx_out *outarg, struct inode *inode, + u64 attr_version, struct kstat *stat) +{ + struct fuse_statx *sx =3D &outarg->stat; + struct fuse_attr attr; =20 - sx =3D &outarg.stat; if (((sx->mask & STATX_SIZE) && !fuse_valid_size(sx->size)) || ((sx->mask & STATX_TYPE) && (!fuse_valid_type(sx->mode) || inode_wrong_type(inode, sx->mode)))) { @@ -1450,10 +1444,10 @@ static int fuse_do_statx(struct mnt_idmap *idmap, s= truct inode *inode, return -EIO; } =20 - fuse_statx_to_attr(&outarg.stat, &attr); + fuse_statx_to_attr(sx, &attr); if ((sx->mask & STATX_BASIC_STATS) =3D=3D STATX_BASIC_STATS) { - fuse_change_attributes(inode, &attr, &outarg.stat, - ATTR_TIMEOUT(&outarg), attr_version); + fuse_change_attributes(inode, &attr, sx, + ATTR_TIMEOUT(outarg), attr_version); } =20 if (stat) { @@ -1467,6 +1461,30 @@ static int fuse_do_statx(struct mnt_idmap *idmap, st= ruct inode *inode, return 0; } =20 +static int fuse_do_statx(struct mnt_idmap *idmap, struct inode *inode, + struct file *file, struct kstat *stat) +{ + int err; + struct fuse_statx_in inarg; + struct fuse_statx_out outarg; + struct fuse_mount *fm =3D get_fuse_mount(inode); + struct fuse_file *ff =3D NULL; + u64 attr_version =3D fuse_get_attr_version(fm->fc); + FUSE_ARGS(args); + + /* Directories have separate file-handle space */ + if (file && S_ISREG(inode->i_mode)) + ff =3D file->private_data; + + fuse_statx_init(&args, &inarg, get_node_id(inode), ff, &outarg); + + err =3D fuse_simple_request(fm, &args); + if (err) + return err; + + return fuse_statx_update(idmap, &outarg, inode, attr_version, stat); +} + /* * Helper function to initialize fuse_args for GETATTR operations */ From nobody Tue Apr 7 12:20:30 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 07744393DD7; Wed, 25 Feb 2026 11:25:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772018721; cv=none; b=mhqENyvUdlh65FIqdyD9FFNr+Jq5vC2QStdJra4dtXU+Jj5aKM8dXogcIYcgIiHqjXn/8AgZGvNABoICoUbxxmFWDxT28CM/vh9HGATpr4dNnteq4gadX5/iDqiRlEs3iCXAVFfALK4VUA5eXZKumXW8UovQ+4a/hhdy9ocaiI8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772018721; c=relaxed/simple; bh=SZpEzGASG+jZt+3SH49zDeqrkrwd3N0OJ7XZE6e/Fm4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=oRz0SVDvZjR4lFjB7GfFbWZjnti20I1YBbKC18yXiYzi8Zlt0D6aO//feJoqEFjFVdxuKQJ4prO4xCmzohpRtAMNdwGAjMI5I6CVhwCxyiHQ/erXTkSt3WnMOKsvAQmA7Gil64fYJhbYoU/jc9HiNaS32Eqz3a7Tg5pl0+YIXus= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=ix7h34B6; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="ix7h34B6" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=2RFtJyoFXoB2y5CfcGqDjmIY95EdGt03PAMHowDwl9M=; b=ix7h34B61QPCnsQBsi/e/CVxJK TvzBDKXyUIR4BiksWQQyiQJdOvTK+2waJJEgk/XAIJYd0HkkwUn4k/qpnzDINZnY/16oXsrKS8e/o os7tOCzmrgYijPHp4a8xyYBXg4o9KSWwkkE7WMvfl9J2zGUghCzLmljSFs9CTvkCfboB4rlMgALwC oSRx5/suP5nOSHbEYfAElbJhUaFY2H+g45goDsratWvaNTFQnKK8IJvR4qIoejIKtkGyagAqedQRs Bsfd8/wXziFQWyOdD0UGTeSVDhr5YqyM/UhSkqxmb0Gm4qgQK9tZT2v8LI4SF3jDt56KwpR4PmVR6 8PJs62tQ==; Received: from bl17-145-117.dsl.telepac.pt ([188.82.145.117] helo=localhost) by fanzine2.igalia.com with utf8esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1vvD0q-005Cmt-Av; Wed, 25 Feb 2026 12:25:04 +0100 From: Luis Henriques To: Miklos Szeredi , Amir Goldstein , Bernd Schubert , Bernd Schubert , "Darrick J. Wong" , Horst Birthelmer , Joanne Koong , Kevin Chen Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Matt Harvey , kernel-dev@igalia.com, Luis Henriques Subject: [RFC PATCH v3 6/8] fuse: implementation of lookup_handle+statx compound operation Date: Wed, 25 Feb 2026 11:24:37 +0000 Message-ID: <20260225112439.27276-7-luis@igalia.com> In-Reply-To: <20260225112439.27276-1-luis@igalia.com> References: <20260225112439.27276-1-luis@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The implementation of lookup_handle+statx compound operation extends the lookup operation so that a file handle is be passed into the kernel. It also needs to include an extra inarg, so that the parent directory file handle can be sent to user-space. This extra inarg is added as an extension header to the request. By having a separate statx including in a compound operation allows the attr to be dropped from the lookup_handle request, simplifying the traditional FUSE lookup operation. Signed-off-by: Luis Henriques --- fs/fuse/dir.c | 294 +++++++++++++++++++++++++++++++++++--- fs/fuse/fuse_i.h | 23 ++- fs/fuse/inode.c | 48 +++++-- fs/fuse/readdir.c | 2 +- include/uapi/linux/fuse.h | 23 ++- 5 files changed, 355 insertions(+), 35 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 5c0f1364c392..7fa8c405f1a3 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -21,6 +21,7 @@ #include #include #include +#include =20 static bool __read_mostly allow_sys_admin_access; module_param(allow_sys_admin_access, bool, 0644); @@ -372,6 +373,47 @@ static void fuse_lookup_init(struct fuse_args *args, u= 64 nodeid, args->out_args[0].value =3D outarg; } =20 +static int do_lookup_handle_statx(struct fuse_mount *fm, u64 parent_nodeid, + struct inode *parent_inode, + const struct qstr *name, + struct fuse_entry2_out *lookup_out, + struct fuse_statx_out *statx_out, + struct fuse_file_handle **fh); +static void fuse_statx_to_attr(struct fuse_statx *sx, struct fuse_attr *at= tr); +static int do_reval_lookup(struct fuse_mount *fm, u64 parent_nodeid, + const struct qstr *name, u64 *nodeid, + u64 *generation, u64 *attr_valid, + struct fuse_attr *attr, struct fuse_file_handle **fh) +{ + struct fuse_entry_out entry_out; + struct fuse_entry2_out lookup_out; + struct fuse_statx_out statx_out; + FUSE_ARGS(lookup_args); + int ret =3D 0; + + if (fm->fc->lookup_handle) { + ret =3D do_lookup_handle_statx(fm, parent_nodeid, NULL, name, + &lookup_out, &statx_out, fh); + if (!ret) { + *nodeid =3D lookup_out.nodeid; + *generation =3D lookup_out.generation; + *attr_valid =3D fuse_time_to_jiffies(lookup_out.entry_valid, + lookup_out.entry_valid_nsec); + fuse_statx_to_attr(&statx_out.stat, attr); + } + } else { + fuse_lookup_init(&lookup_args, parent_nodeid, name, &entry_out); + ret =3D fuse_simple_request(fm, &lookup_args); + if (!ret) { + *nodeid =3D entry_out.nodeid; + *generation =3D entry_out.generation; + *attr_valid =3D ATTR_TIMEOUT(&entry_out); + memcpy(attr, &entry_out.attr, sizeof(*attr)); + } + } + + return ret; +} /* * Check whether the dentry is still valid * @@ -399,10 +441,11 @@ static int fuse_dentry_revalidate(struct inode *dir, = const struct qstr *name, goto invalid; else if (time_before64(fuse_dentry_time(entry), get_jiffies_64()) || (flags & (LOOKUP_EXCL | LOOKUP_REVAL | LOOKUP_RENAME_TARGET))) { - struct fuse_entry_out outarg; - FUSE_ARGS(args); struct fuse_forget_link *forget; + struct fuse_file_handle *fh =3D NULL; u64 attr_version; + u64 nodeid, generation, attr_valid; + struct fuse_attr attr; =20 /* For negative dentries, always do a fresh lookup */ if (!inode) @@ -421,35 +464,36 @@ static int fuse_dentry_revalidate(struct inode *dir, = const struct qstr *name, =20 attr_version =3D fuse_get_attr_version(fm->fc); =20 - fuse_lookup_init(&args, get_node_id(dir), name, &outarg); - ret =3D fuse_simple_request(fm, &args); + ret =3D do_reval_lookup(fm, get_node_id(dir), name, &nodeid, + &generation, &attr_valid, &attr, &fh); /* Zero nodeid is same as -ENOENT */ - if (!ret && !outarg.nodeid) + if (!ret && !nodeid) ret =3D -ENOENT; if (!ret) { fi =3D get_fuse_inode(inode); - if (outarg.nodeid !=3D get_node_id(inode) || - (bool) IS_AUTOMOUNT(inode) !=3D (bool) (outarg.attr.flags & FUSE_AT= TR_SUBMOUNT)) { - fuse_queue_forget(fm->fc, forget, - outarg.nodeid, 1); + if (!fuse_file_handle_is_equal(fm->fc, fi->fh, fh) || + nodeid !=3D get_node_id(inode) || + (bool) IS_AUTOMOUNT(inode) !=3D (bool) (attr.flags & FUSE_ATTR_SUBM= OUNT)) { + fuse_queue_forget(fm->fc, forget, nodeid, 1); + kfree(fh); goto invalid; } spin_lock(&fi->lock); fi->nlookup++; spin_unlock(&fi->lock); } + kfree(fh); kfree(forget); if (ret =3D=3D -ENOMEM || ret =3D=3D -EINTR) goto out; - if (ret || fuse_invalid_attr(&outarg.attr) || - fuse_stale_inode(inode, outarg.generation, &outarg.attr)) + if (ret || fuse_invalid_attr(&attr) || + fuse_stale_inode(inode, generation, &attr)) goto invalid; =20 forget_all_cached_acls(inode); - fuse_change_attributes(inode, &outarg.attr, NULL, - ATTR_TIMEOUT(&outarg), + fuse_change_attributes(inode, &attr, NULL, attr_valid, attr_version); - fuse_change_entry_timeout(entry, &outarg); + fuse_dentry_settime(entry, attr_valid); } else if (inode) { fi =3D get_fuse_inode(inode); if (flags & LOOKUP_RCU) { @@ -546,8 +590,215 @@ bool fuse_invalid_attr(struct fuse_attr *attr) return !fuse_valid_type(attr->mode) || !fuse_valid_size(attr->size); } =20 -int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr= *name, - u64 *time, struct inode **inode) +static int create_ext_handle(struct fuse_in_arg *ext, struct fuse_inode *f= i) +{ + struct fuse_ext_header *xh; + struct fuse_file_handle *fh; + u32 len; + + len =3D fuse_ext_size(sizeof(*fi->fh) + fi->fh->size); + xh =3D fuse_extend_arg(ext, len); + if (!xh) + return -ENOMEM; + + xh->size =3D len; + xh->type =3D FUSE_EXT_HANDLE; + fh =3D (struct fuse_file_handle *)&xh[1]; + fh->size =3D fi->fh->size; + memcpy(fh->handle, fi->fh->handle, fh->size); + + return 0; +} + +static int fuse_lookup_handle_init(struct fuse_args *args, u64 nodeid, + struct fuse_inode *fi, + const struct qstr *name, + struct fuse_entry2_out *outarg) +{ + struct fuse_file_handle *fh; + size_t fh_size =3D sizeof(*fh) + MAX_HANDLE_SZ; + int ret =3D -ENOMEM; + + fh =3D kzalloc(fh_size, GFP_KERNEL); + if (!fh) + return ret; + + memset(outarg, 0, sizeof(struct fuse_entry2_out)); + args->opcode =3D FUSE_LOOKUP_HANDLE; + args->nodeid =3D nodeid; + args->in_numargs =3D 3; + fuse_set_zero_arg0(args); + args->in_args[1].size =3D name->len; + args->in_args[1].value =3D name->name; + args->in_args[2].size =3D 1; + args->in_args[2].value =3D ""; + if (fi && fi->fh) { + args->is_ext =3D true; + args->ext_idx =3D args->in_numargs++; + args->in_args[args->ext_idx].size =3D 0; + ret =3D create_ext_handle(&args->in_args[args->ext_idx], fi); + if (ret) { + kfree(fh); + return ret; + } + } + args->out_numargs =3D 2; + args->out_argvar =3D true; + args->out_argvar_idx =3D 1; + args->out_args[0].size =3D sizeof(struct fuse_entry2_out); + args->out_args[0].value =3D outarg; + + /* XXX do allocation to the actual size of the handle */ + args->out_args[1].size =3D fh_size; + args->out_args[1].value =3D fh; + + return 0; +} + +static void fuse_req_free_argvar_ext(struct fuse_args *args) +{ + if (args->out_argvar) + kfree(args->out_args[args->out_argvar_idx].value); + if (args->is_ext) + kfree(args->in_args[args->ext_idx].value); +} + +static void fuse_statx_init(struct fuse_args *args, struct fuse_statx_in *= inarg, + u64 nodeid, struct fuse_file *ff, + struct fuse_statx_out *outarg); +static int fuse_statx_update(struct mnt_idmap *idmap, + struct fuse_statx_out *outarg, struct inode *inode, + u64 attr_version, struct kstat *stat); +static int do_lookup_handle_statx(struct fuse_mount *fm, u64 parent_nodeid, + struct inode *parent_inode, + const struct qstr *name, + struct fuse_entry2_out *lookup_out, + struct fuse_statx_out *statx_out, + struct fuse_file_handle **fh) +{ + struct fuse_compound_req *compound; + struct fuse_inode *fi =3D NULL; + struct fuse_statx_in statx_in; + FUSE_ARGS(lookup_args); + FUSE_ARGS(statx_args); + int ret; + + if (parent_inode) + fi =3D get_fuse_inode(parent_inode); + + compound =3D fuse_compound_alloc(fm, 0); + if (!compound) + return -ENOMEM; + + ret =3D fuse_lookup_handle_init(&lookup_args, parent_nodeid, fi, + name, lookup_out); + if (ret) + goto out_compound; + + ret =3D fuse_compound_add(compound, &lookup_args); + if (ret) + goto out_args; + + /* + * XXX nodeid is the parent of the inode we want! At this point + * we still don't have the nodeid. Using FUSE_ROOT_ID for now. + */ + fuse_statx_init(&statx_args, &statx_in, FUSE_ROOT_ID, NULL, statx_out); + ret =3D fuse_compound_add(compound, &statx_args); + if (ret) + goto out_args; + + ret =3D fuse_compound_send(compound); + if (ret) + goto out_args; + + ret =3D fuse_compound_get_error(compound, 0); + if (ret || !lookup_out->nodeid) + goto out_args; + if (lookup_out->nodeid =3D=3D FUSE_ROOT_ID && + lookup_out->generation !=3D 0) { + pr_warn_once("root generation should be zero\n"); + lookup_out->generation =3D 0; + } + if ((lookup_args.out_args[1].size > 0) && + (lookup_args.out_args[1].value)) { + struct fuse_file_handle *h =3D lookup_args.out_args[1].value; + + *fh =3D kzalloc(sizeof(*fh) + h->size, GFP_KERNEL); + if (!*fh) { + ret =3D -ENOMEM; + goto out_args; + } + (*fh)->size =3D h->size; + memcpy((*fh)->handle, h->handle, (*fh)->size); + } + + ret =3D fuse_compound_get_error(compound, 1); + if (ret) { + kfree(*fh); + *fh =3D NULL; + } + +out_args: + fuse_req_free_argvar_ext(&lookup_args); +out_compound: + kfree(compound); + + return ret; +} + +static int fuse_lookup_handle_name(struct super_block *sb, u64 nodeid, + struct inode *dir, const struct qstr *name, + u64 *time, struct inode **inode) +{ + struct fuse_mount *fm =3D get_fuse_mount_super(sb); + struct fuse_file_handle *fh =3D NULL; + struct fuse_entry2_out lookup_out; + struct fuse_statx_out statx_out; + struct fuse_attr attr; + struct fuse_forget_link *forget; + u64 attr_version, evict_ctr; + int ret; + + forget =3D fuse_alloc_forget(); + if (!forget) + return -ENOMEM; + + attr_version =3D fuse_get_attr_version(fm->fc); + evict_ctr =3D fuse_get_evict_ctr(fm->fc); + + ret =3D do_lookup_handle_statx(fm, nodeid, dir, name, &lookup_out, + &statx_out, &fh); + if (ret) + goto out_forget; + + fuse_statx_to_attr(&statx_out.stat, &attr); + WARN_ON(fuse_invalid_attr(&attr)); + + *inode =3D fuse_iget(sb, lookup_out.nodeid, lookup_out.generation, + &attr, ATTR_TIMEOUT(&statx_out), + attr_version, evict_ctr, fh); + ret =3D -ENOMEM; + if (!*inode) { + fuse_queue_forget(fm->fc, forget, lookup_out.nodeid, 1); + goto out_forget; + } + if (time) + *time =3D fuse_time_to_jiffies(lookup_out.entry_valid, + lookup_out.entry_valid_nsec); + + /* XXX idmap? */ + ret =3D fuse_statx_update(&nop_mnt_idmap, &statx_out, *inode, + attr_version, NULL); + +out_forget: + kfree(forget); + + return ret; +} + +int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct inode *dir, + const struct qstr *name, u64 *time, struct inode **inode) { struct fuse_mount *fm =3D get_fuse_mount_super(sb); FUSE_ARGS(args); @@ -561,6 +812,9 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid= , const struct qstr *name if (name->len > fm->fc->name_max) goto out; =20 + if (fm->fc->lookup_handle) + return fuse_lookup_handle_name(sb, nodeid, dir, name, time, + inode); =20 forget =3D fuse_alloc_forget(); err =3D -ENOMEM; @@ -586,7 +840,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid= , const struct qstr *name =20 *inode =3D fuse_iget(sb, outarg.nodeid, outarg.generation, &outarg.attr, ATTR_TIMEOUT(&outarg), - attr_version, evict_ctr); + attr_version, evict_ctr, NULL); err =3D -ENOMEM; if (!*inode) { fuse_queue_forget(fm->fc, forget, outarg.nodeid, 1); @@ -621,7 +875,7 @@ static struct dentry *fuse_lookup(struct inode *dir, st= ruct dentry *entry, epoch =3D atomic_read(&fc->epoch); =20 locked =3D fuse_lock_inode(dir); - err =3D fuse_lookup_name(dir->i_sb, get_node_id(dir), &entry->d_name, + err =3D fuse_lookup_name(dir->i_sb, get_node_id(dir), dir, &entry->d_name, &time, &inode); fuse_unlock_inode(dir, locked); if (err =3D=3D -ENOENT) @@ -882,7 +1136,7 @@ static int fuse_create_open(struct mnt_idmap *idmap, s= truct inode *dir, ff->nodeid =3D outentry.nodeid; ff->open_flags =3D outopenp->open_flags; inode =3D fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation, - &outentry.attr, ATTR_TIMEOUT(&outentry), 0, 0); + &outentry.attr, ATTR_TIMEOUT(&outentry), 0, 0, NULL); if (!inode) { flags &=3D ~(O_CREAT | O_EXCL | O_TRUNC); fuse_sync_release(NULL, ff, flags); @@ -1009,7 +1263,7 @@ static struct dentry *create_new_entry(struct mnt_idm= ap *idmap, struct fuse_moun goto out_put_forget_req; =20 inode =3D fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation, - &outarg.attr, ATTR_TIMEOUT(&outarg), 0, 0); + &outarg.attr, ATTR_TIMEOUT(&outarg), 0, 0, NULL); if (!inode) { fuse_queue_forget(fm->fc, forget, outarg.nodeid, 1); return ERR_PTR(-ENOMEM); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 04f09e2ccfd0..173032887fc2 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -223,6 +223,8 @@ struct fuse_inode { * so preserve the blocksize specified by the server. */ u8 cached_i_blkbits; + + struct fuse_file_handle *fh; }; =20 /** FUSE inode state bits */ @@ -923,6 +925,9 @@ struct fuse_conn { /* Is synchronous FUSE_INIT allowed? */ unsigned int sync_init:1; =20 + /** Is LOOKUP_HANDLE implemented by the fs? */ + unsigned int lookup_handle:1; + /* Use io_uring for communication */ unsigned int io_uring; =20 @@ -1072,6 +1077,18 @@ static inline int invalid_nodeid(u64 nodeid) return !nodeid || nodeid =3D=3D FUSE_ROOT_ID; } =20 +static inline bool fuse_file_handle_is_equal(struct fuse_conn *fc, + struct fuse_file_handle *fh1, + struct fuse_file_handle *fh2) +{ + if (!fc->lookup_handle || + ((fh1 =3D=3D fh2) && !fh1) || /* both NULL */ + (fh1 && fh2 && (fh1->size =3D=3D fh2->size) && + (!memcmp(fh1->handle, fh2->handle, fh1->size)))) + return true; + return false; +} + static inline u64 fuse_get_attr_version(struct fuse_conn *fc) { return atomic64_read(&fc->attr_version); @@ -1148,10 +1165,10 @@ extern const struct dentry_operations fuse_dentry_o= perations; 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); + u64 evict_ctr, struct fuse_file_handle *fh); =20 -int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr= *name, - u64 *time, struct inode **inode); +int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct inode *dir, + const struct qstr *name, u64 *time, struct inode **inode); =20 /** * Send FORGET command diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 006436a3ad06..9f2c0c9e877c 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -120,6 +120,8 @@ static struct inode *fuse_alloc_inode(struct super_bloc= k *sb) if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH)) fuse_inode_backing_set(fi, NULL); =20 + fi->fh =3D NULL; + return &fi->inode; =20 out_free_forget: @@ -141,6 +143,8 @@ static void fuse_free_inode(struct inode *inode) if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH)) fuse_backing_put(fuse_inode_backing(fi)); =20 + kfree(fi->fh); + kmem_cache_free(fuse_inode_cachep, fi); } =20 @@ -465,7 +469,7 @@ static int fuse_inode_set(struct inode *inode, void *_n= odeidp) 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) + u64 evict_ctr, struct fuse_file_handle *fh) { struct inode *inode; struct fuse_inode *fi; @@ -505,6 +509,26 @@ struct inode *fuse_iget(struct super_block *sb, u64 no= deid, if (!inode) return NULL; =20 + fi =3D get_fuse_inode(inode); + if (fc->lookup_handle && !fi->fh) { + if (!fh && (nodeid !=3D FUSE_ROOT_ID)) { + pr_err("NULL file handle for nodeid %llu\n", nodeid); + WARN_ON_ONCE(1); + } else { + size_t sz =3D sizeof(struct fuse_file_handle); + + if (fh) + sz +=3D fh->size; + + fi->fh =3D kzalloc(sz, GFP_KERNEL); + if (!fi->fh) { + iput(inode); + return NULL; // ENOMEM + } + if (fh) + memcpy(fi->fh, fh, sz); + } + } if ((inode_state_read_once(inode) & I_NEW)) { inode->i_flags |=3D S_NOATIME; if (!fc->writeback_cache || !S_ISREG(attr->mode)) @@ -512,7 +536,8 @@ struct inode *fuse_iget(struct super_block *sb, u64 nod= eid, inode->i_generation =3D generation; fuse_init_inode(inode, attr, fc); unlock_new_inode(inode); - } else if (fuse_stale_inode(inode, generation, attr)) { + } else if (fuse_stale_inode(inode, generation, attr) || + (!fuse_file_handle_is_equal(fc, fi->fh, fh))) { /* nodeid was reused, any I/O on the old inode should fail */ fuse_make_bad(inode); if (inode !=3D d_inode(sb->s_root)) { @@ -521,7 +546,6 @@ struct inode *fuse_iget(struct super_block *sb, u64 nod= eid, goto retry; } } - fi =3D get_fuse_inode(inode); spin_lock(&fi->lock); fi->nlookup++; spin_unlock(&fi->lock); @@ -1068,7 +1092,7 @@ static struct inode *fuse_get_root_inode(struct super= _block *sb, unsigned int mo attr.mode =3D mode; attr.ino =3D FUSE_ROOT_ID; attr.nlink =3D 1; - return fuse_iget(sb, FUSE_ROOT_ID, 0, &attr, 0, 0, 0); + return fuse_iget(sb, FUSE_ROOT_ID, 0, &attr, 0, 0, 0, NULL); } =20 struct fuse_inode_handle { @@ -1094,7 +1118,8 @@ static struct dentry *fuse_get_dentry(struct super_bl= ock *sb, if (!fc->export_support) goto out_err; =20 - err =3D fuse_lookup_name(sb, handle->nodeid, &name, NULL, &inode); + err =3D fuse_lookup_name(sb, handle->nodeid, NULL, &name, NULL, + &inode); if (err && err !=3D -ENOENT) goto out_err; if (err || !inode) { @@ -1115,9 +1140,9 @@ static struct dentry *fuse_get_dentry(struct super_bl= ock *sb, =20 return entry; =20 - out_iput: +out_iput: iput(inode); - out_err: +out_err: return ERR_PTR(err); } =20 @@ -1194,7 +1219,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, NULL, &inode); + child_inode, &dotdot_name, NULL, &inode); if (err) { if (err =3D=3D -ENOENT) return ERR_PTR(-ESTALE); @@ -1459,6 +1484,9 @@ static void process_init_reply(struct fuse_mount *fm,= struct fuse_args *args, =20 if (flags & FUSE_REQUEST_TIMEOUT) timeout =3D arg->request_timeout; + + if (flags & FUSE_HAS_LOOKUP_HANDLE) + fc->lookup_handle =3D 1; } else { ra_pages =3D fc->max_read / PAGE_SIZE; fc->no_lock =3D 1; @@ -1509,7 +1537,7 @@ static struct fuse_init_args *fuse_new_init(struct fu= se_mount *fm) FUSE_SECURITY_CTX | FUSE_CREATE_SUPP_GROUP | FUSE_HAS_EXPIRE_ONLY | FUSE_DIRECT_IO_ALLOW_MMAP | FUSE_NO_EXPORT_SUPPORT | FUSE_HAS_RESEND | FUSE_ALLOW_IDMAP | - FUSE_REQUEST_TIMEOUT; + FUSE_REQUEST_TIMEOUT | FUSE_LOOKUP_HANDLE; #ifdef CONFIG_FUSE_DAX if (fm->fc->dax) flags |=3D FUSE_MAP_ALIGNMENT; @@ -1745,7 +1773,7 @@ static int fuse_fill_super_submount(struct super_bloc= k *sb, =20 fuse_fill_attr_from_inode(&root_attr, parent_fi); root =3D fuse_iget(sb, parent_fi->nodeid, 0, &root_attr, 0, 0, - fuse_get_evict_ctr(fm->fc)); + fuse_get_evict_ctr(fm->fc), NULL); /* * This inode is just a duplicate, so it is not looked up and * its nlookup should not be incremented. fuse_iget() does diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c index c2aae2eef086..2b59a196bcbf 100644 --- a/fs/fuse/readdir.c +++ b/fs/fuse/readdir.c @@ -235,7 +235,7 @@ static int fuse_direntplus_link(struct file *file, } else { inode =3D fuse_iget(dir->i_sb, o->nodeid, o->generation, &o->attr, ATTR_TIMEOUT(o), - attr_version, evict_ctr); + attr_version, evict_ctr, NULL); if (!inode) inode =3D ERR_PTR(-ENOMEM); =20 diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index 113583c4efb6..89e6176abe25 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -240,6 +240,9 @@ * - add FUSE_COPY_FILE_RANGE_64 * - add struct fuse_copy_file_range_out * - add FUSE_NOTIFY_PRUNE + * + * 7.46 + * - add FUSE_LOOKUP_HANDLE */ =20 #ifndef _LINUX_FUSE_H @@ -275,7 +278,7 @@ #define FUSE_KERNEL_VERSION 7 =20 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 45 +#define FUSE_KERNEL_MINOR_VERSION 46 =20 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -495,6 +498,7 @@ struct fuse_file_lock { #define FUSE_ALLOW_IDMAP (1ULL << 40) #define FUSE_OVER_IO_URING (1ULL << 41) #define FUSE_REQUEST_TIMEOUT (1ULL << 42) +#define FUSE_HAS_LOOKUP_HANDLE (1ULL << 43) =20 /** * CUSE INIT request/reply flags @@ -609,6 +613,7 @@ enum fuse_ext_type { /* Types 0..31 are reserved for fuse_secctx_header */ FUSE_MAX_NR_SECCTX =3D 31, FUSE_EXT_GROUPS =3D 32, + FUSE_EXT_HANDLE =3D 33, }; =20 enum fuse_opcode { @@ -671,6 +676,8 @@ enum fuse_opcode { */ FUSE_COMPOUND =3D 54, =20 + FUSE_LOOKUP_HANDLE =3D 55, + /* CUSE specific operations */ CUSE_INIT =3D 4096, =20 @@ -707,6 +714,20 @@ struct fuse_entry_out { struct fuse_attr attr; }; =20 +struct fuse_entry2_out { + uint64_t nodeid; + uint64_t generation; + uint64_t entry_valid; + uint32_t entry_valid_nsec; + uint32_t flags; + uint64_t spare; +}; + +struct fuse_file_handle { + uint16_t size; + uint8_t handle[]; +}; + struct fuse_forget_in { uint64_t nlookup; }; From nobody Tue Apr 7 12:20:30 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5E38A393DE7; Wed, 25 Feb 2026 11:25:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772018721; cv=none; b=AGm3mBmEHmPhdATUs0t/8BYanG/2uNEmprKG+BmUC+NdTW6Nz339YcHvcjTXqKJUOswrP9K5xtj32Dc/d89JFepjxjShdqnrskvueYXci062lBL5Xy07GaWC/kKxvF7LE/cKaD1jhw/9a5dfVPqs4O+pFPvq4ovu6AE0HqEcwMU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772018721; c=relaxed/simple; bh=LWhFIDyxCL4zSWPwychKIFM78yk9+CM8VA7ArlCfhpM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=dJtak4dnTDeo7yCPicuZBA9q0UQmMO5ia5yFOMsz9EKQcWkrGvGy6JRNnqw9Pat9tGzNyySGMQan97nLfTEQuF8RSSpCd0hZXi2pML+JBzTHalfMeFmyWYPLiv/LkJoCcq88zEk4f8OC0oStb9OKjeUs9XeeZrFCT2esF8K247o= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=IVvDgyBK; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="IVvDgyBK" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=PA0U99V8U2+zRTsAYsXjZi6/myHrUg2mZVBgwWFjUrg=; b=IVvDgyBK7SJYXaLGrbN6mSmQk1 tAG6I0feXWYKPupkOvh6BlgTeZ8CEjy1O00hoFVLVLahUMLP8CHvDN0Y8kYu7oRT5jrL9Va9nc7Na bMbRV2FUoGH+7UOfY5AWL3aus6eUynroij/MNT68DRU1VpbV/3w7Fau/kGA80WxHroMFeqKYyq538 vjVPYFbwrD0/4W5uqYvO5Ue1Ei3pyA45S26ul0hObezkTMBGELO+DbGhKBDBdyfSXG+yF3S/fJHZU iUGTh27I8nV3SrIA2D/g6eBOom2Ita+LbOAUnSZ7co8/cOij2Xrl3kLEmWk53pducaMoGCLuH+bss +67//mQA==; Received: from bl17-145-117.dsl.telepac.pt ([188.82.145.117] helo=localhost) by fanzine2.igalia.com with utf8esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1vvD0r-005Cn1-1Y; Wed, 25 Feb 2026 12:25:05 +0100 From: Luis Henriques To: Miklos Szeredi , Amir Goldstein , Bernd Schubert , Bernd Schubert , "Darrick J. Wong" , Horst Birthelmer , Joanne Koong , Kevin Chen Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Matt Harvey , kernel-dev@igalia.com, Luis Henriques Subject: [RFC PATCH v3 7/8] fuse: export fuse_open_args_fill() helper function Date: Wed, 25 Feb 2026 11:24:38 +0000 Message-ID: <20260225112439.27276-8-luis@igalia.com> In-Reply-To: <20260225112439.27276-1-luis@igalia.com> References: <20260225112439.27276-1-luis@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Allow fuse_open_args_fill() fuction to be used from different files. Signed-off-by: Luis Henriques --- fs/fuse/file.c | 2 +- fs/fuse/fuse_i.h | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 1045d74dd95f..ea9023150a38 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -26,7 +26,7 @@ /* * Helper function to initialize fuse_args for OPEN/OPENDIR operations */ -static void fuse_open_args_fill(struct fuse_args *args, u64 nodeid, int op= code, +void fuse_open_args_fill(struct fuse_args *args, u64 nodeid, int opcode, struct fuse_open_in *inarg, struct fuse_open_out *outarg) { args->opcode =3D opcode; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 173032887fc2..f37959c76412 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1588,10 +1588,14 @@ int fuse_file_io_open(struct file *file, struct ino= de *inode); void fuse_file_io_release(struct fuse_file *ff, struct inode *inode); =20 /* file.c */ +void fuse_open_args_fill(struct fuse_args *args, u64 nodeid, int opcode, + struct fuse_open_in *inarg, + struct fuse_open_out *outarg); + struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid, - struct inode *inode, - unsigned int open_flags, - bool isdir); + struct inode *inode, + unsigned int open_flags, + bool isdir); void fuse_file_release(struct inode *inode, struct fuse_file *ff, unsigned int open_flags, fl_owner_t id, bool isdir); From nobody Tue Apr 7 12:20:30 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7E7ED392C27; Wed, 25 Feb 2026 11:25:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772018722; cv=none; b=FU1Ra/zyj6w2xYYdtAIN41gnL/qEwskdmdk2lx9dQ0nuaRoy7uKaIs64zc2K/ElF5foSQFA7ymVUXRALJLJtku24j5jI45GIBDhmQWYpf3JHOKaAmxrxCOmMmyfxa7hAQQaQG1lyRa7DlhEu8/IWVeRbYhuYrLejMQQ8RKD4ALs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772018722; c=relaxed/simple; bh=uTCDxnliXjXAvJsTB1w8ldDKDoufhdpN+R7hxmo5690=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=gOc3Ba5F7CCvcLoE+yhaRt0rbI05hIyUHnjyJYMG9Ro/yADDk0TEGAo7z7PcPMM7jHloinJqF8XvUTA0BG3GFknH6Tu2RL7o4YiRgGAEULdFuwwSQG1HlclYvvx2cD5EarLuF2z1DduKMsIkoCqIlLvp13qNVsZM1fvI/v3Excw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=ZX0ynIsd; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="ZX0ynIsd" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=ZGtFr/zsghreObQeMbgsnvUH/bl2kVCNT/F2RN08XwE=; b=ZX0ynIsdiTAiOMLkYUxu59t/Q6 SKK+oPiQ2sdqONEXtGsUMJq6VA+RdzzVOp+8NvNBNM9dMnzwp8Rsu9FHvZc0VdQQLGKiqBgiOJPe9 h7mVNg+potvCfBIitZJ1ZSBQAZ4g0gDwiFFmHG0M7JyVYAXPc1yGp3sQNZd+IgPF7N7sz43RSEk3i fZsv3o5DeJEQLAyoIkFqiuzD2fjK9yHiiuErrS4Xi0AnI5saUwaZdXX8EyS1ON1TYWzqX0vDNP1VP QLjUWmPojdvDVTMeSNS/IIE+R6MOLxDtADnzuv+SEi/0zLFRNvSzmn10/vKF4Iw52ejSDYD1IMBno S6UovmTA==; Received: from bl17-145-117.dsl.telepac.pt ([188.82.145.117] helo=localhost) by fanzine2.igalia.com with utf8esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1vvD0s-005CnB-Ra; Wed, 25 Feb 2026 12:25:06 +0100 From: Luis Henriques To: Miklos Szeredi , Amir Goldstein , Bernd Schubert , Bernd Schubert , "Darrick J. Wong" , Horst Birthelmer , Joanne Koong , Kevin Chen Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Matt Harvey , kernel-dev@igalia.com, Luis Henriques Subject: [RFC PATCH v3 8/8] fuse: implementation of mkobj_handle+statx+open compound operation Date: Wed, 25 Feb 2026 11:24:39 +0000 Message-ID: <20260225112439.27276-9-luis@igalia.com> In-Reply-To: <20260225112439.27276-1-luis@igalia.com> References: <20260225112439.27276-1-luis@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The implementation of this compound operation allows atomic_open() to use file handle. It also introduces a new MKOBJ_HANDLE operation that will handle the file system object creation and will return the file handle. The atomicity of the operation (create + open) needs to be handled in user-space (e.g. the handling of the O_EXCL flag). Signed-off-by: Luis Henriques --- fs/fuse/dir.c | 219 +++++++++++++++++++++++++++++++++++++- include/uapi/linux/fuse.h | 2 + 2 files changed, 220 insertions(+), 1 deletion(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 7fa8c405f1a3..b5beb1d62c3d 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1173,6 +1173,220 @@ static int fuse_create_open(struct mnt_idmap *idmap= , struct inode *dir, return err; } =20 +static int fuse_mkobj_handle_init(struct fuse_mount *fm, struct fuse_args = *args, + struct mnt_idmap *idmap, struct inode *dir, + struct dentry *entry, unsigned int flags, + umode_t mode, + struct fuse_create_in *inarg, + struct fuse_entry2_out *outarg, + struct fuse_file_handle **fh) +{ + struct fuse_inode *fi; + size_t fh_size =3D sizeof(struct fuse_file_handle) + MAX_HANDLE_SZ; + int err =3D 0; + + *fh =3D kzalloc(fh_size, GFP_KERNEL); + if (!*fh) + return -ENOMEM; + + memset(inarg, 0, sizeof(*inarg)); + memset(outarg, 0, sizeof(*outarg)); + + inarg->flags =3D flags; + inarg->mode =3D mode; + inarg->umask =3D current_umask(); + + if (fm->fc->handle_killpriv_v2 && (flags & O_TRUNC) && + !(flags & O_EXCL) && !capable(CAP_FSETID)) + inarg->open_flags |=3D FUSE_OPEN_KILL_SUIDGID; + + args->opcode =3D FUSE_MKOBJ_HANDLE; + args->nodeid =3D get_node_id(dir); + args->in_numargs =3D 2; + args->in_args[0].size =3D sizeof(*inarg); + args->in_args[0].value =3D inarg; + args->in_args[1].size =3D entry->d_name.len + 1; + args->in_args[1].value =3D entry->d_name.name; + + err =3D get_create_ext(idmap, args, dir, entry, mode); + if (err) + goto out_err; + fi =3D get_fuse_inode(dir); + if (fi && fi->fh) { + if (!args->is_ext) { + args->is_ext =3D true; + args->ext_idx =3D args->in_numargs++; + } + err =3D create_ext_handle(&args->in_args[args->ext_idx], fi); + if (err) + goto out_err; + } + + args->out_numargs =3D 2; + args->out_args[0].size =3D sizeof(*outarg); + args->out_args[0].value =3D outarg; + args->out_args[1].size =3D fh_size; + args->out_args[1].value =3D *fh; + +out_err: + if (err) { + kfree(*fh); + free_ext_value(args); + } + + return err; +} + +static int fuse_mkobj_statx_open(struct mnt_idmap *idmap, struct inode *di= r, + struct dentry *entry, struct file *file, + unsigned int flags, umode_t mode) +{ + struct fuse_compound_req *compound; + struct fuse_mount *fm =3D get_fuse_mount(dir); + struct fuse_inode *fi =3D NULL; + struct fuse_create_in mkobj_in; + struct fuse_entry2_out mkobj_out; + struct fuse_statx_in statx_in; + struct fuse_statx_out statx_out; + struct fuse_open_in open_in; + struct fuse_open_out *open_outp; + FUSE_ARGS(mkobj_args); + FUSE_ARGS(statx_args); + FUSE_ARGS(open_args); + struct fuse_forget_link *forget; + struct fuse_file *ff; + struct fuse_attr attr; + struct fuse_file_handle *fh =3D NULL; + struct inode *inode; + int epoch, ret =3D -EIO; + int i; + + epoch =3D atomic_read(&fm->fc->epoch); + + ret =3D -ENOMEM; + forget =3D fuse_alloc_forget(); + if (!forget) + return -ENOMEM; + ff =3D fuse_file_alloc(fm, true); + if (!ff) + goto out_forget; + + if (!fm->fc->dont_mask) + mode &=3D ~current_umask(); + + flags &=3D ~O_NOCTTY; + + compound =3D fuse_compound_alloc(fm, FUSE_COMPOUND_ATOMIC); + if (!compound) + goto out_free_ff; + + fi =3D get_fuse_inode(dir); + if (!fi) { + ret =3D -EIO; + goto out_compound; + } + ret =3D fuse_mkobj_handle_init(fm, &mkobj_args, idmap, dir, entry, flags, + mode, &mkobj_in, &mkobj_out, &fh); + if (ret) + goto out_compound; + + ret =3D fuse_compound_add(compound, &mkobj_args); + if (ret) + goto out_mkobj_args; + + fuse_statx_init(&statx_args, &statx_in, FUSE_ROOT_ID, NULL, &statx_out); + ret =3D fuse_compound_add(compound, &statx_args); + if (ret) + goto out_mkobj_args; + + ff->fh =3D 0; + ff->open_flags =3D FOPEN_KEEP_CACHE; + memset(&open_in, 0, sizeof(open_in)); + + /* XXX flags handling */ + open_in.flags =3D ff->open_flags & ~(O_CREAT | O_EXCL | O_NOCTTY); + if (!fm->fc->atomic_o_trunc) + open_in.flags &=3D ~O_TRUNC; + if (fm->fc->handle_killpriv_v2 && + (open_in.flags & O_TRUNC) && !capable(CAP_FSETID)) + open_in.open_flags |=3D FUSE_OPEN_KILL_SUIDGID; + + open_outp =3D &ff->args->open_outarg; + fuse_open_args_fill(&open_args, FUSE_ROOT_ID, FUSE_OPEN, &open_in, + open_outp); + + ret =3D fuse_compound_add(compound, &open_args); + if (ret) + goto out_mkobj_args; + + ret =3D fuse_compound_send(compound); + if (ret) + goto out_mkobj_args; + + for (i =3D 0; i < 3; i++) { + int err; + + err =3D fuse_compound_get_error(compound, i); + if (err && !ret) + ret =3D err; + } + if (ret) + goto out_mkobj_args; + + fuse_statx_to_attr(&statx_out.stat, &attr); + WARN_ON(fuse_invalid_attr(&attr)); + ret =3D -EIO; + if (!S_ISREG(attr.mode) || invalid_nodeid(mkobj_out.nodeid) || + fuse_invalid_attr(&attr)) + goto out_mkobj_args; + + ff->fh =3D open_outp->fh; + ff->nodeid =3D mkobj_out.nodeid; + ff->open_flags =3D open_outp->open_flags; + inode =3D fuse_iget(dir->i_sb, mkobj_out.nodeid, mkobj_out.generation, + &attr, ATTR_TIMEOUT(&statx_out), 0, 0, fh); + if (!inode) { + flags &=3D ~(O_CREAT | O_EXCL | O_TRUNC); + fuse_sync_release(NULL, ff, flags); + fuse_queue_forget(fm->fc, forget, mkobj_out.nodeid, 1); + ret =3D -ENOMEM; + goto out_mkobj_args; + } + d_instantiate(entry, inode); + + entry->d_time =3D epoch; + fuse_dentry_settime(entry, + fuse_time_to_jiffies(mkobj_out.entry_valid, + mkobj_out.entry_valid_nsec)); + fuse_dir_changed(dir); + ret =3D generic_file_open(inode, file); + if (!ret) { + file->private_data =3D ff; + ret =3D finish_open(file, entry, fuse_finish_open); + } + if (ret) { + fuse_sync_release(get_fuse_inode(inode), ff, flags); + } else { + if (fm->fc->atomic_o_trunc && (flags & O_TRUNC)) + truncate_pagecache(inode, 0); + else if (!(ff->open_flags & FOPEN_KEEP_CACHE)) + invalidate_inode_pages2(inode->i_mapping); + } + +out_mkobj_args: + fuse_req_free_argvar_ext(&mkobj_args); +out_compound: + kfree(compound); +out_free_ff: + if (ret) + fuse_file_free(ff); +out_forget: + kfree(forget); + kfree(fh); + + return ret; +} + static int fuse_mknod(struct mnt_idmap *, struct inode *, struct dentry *, umode_t, dev_t); static int fuse_atomic_open(struct inode *dir, struct dentry *entry, @@ -1201,7 +1415,10 @@ static int fuse_atomic_open(struct inode *dir, struc= t dentry *entry, if (fc->no_create) goto mknod; =20 - err =3D fuse_create_open(idmap, dir, entry, file, flags, mode, FUSE_CREAT= E); + if (fc->lookup_handle) + err =3D fuse_mkobj_statx_open(idmap, dir, entry, file, flags, mode); + else + err =3D fuse_create_open(idmap, dir, entry, file, flags, mode, FUSE_CREA= TE); if (err =3D=3D -ENOSYS) { fc->no_create =3D 1; goto mknod; diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index 89e6176abe25..f49eb1b8f2f3 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -243,6 +243,7 @@ * * 7.46 * - add FUSE_LOOKUP_HANDLE + * - add FUSE_MKOBJ_HANDLE */ =20 #ifndef _LINUX_FUSE_H @@ -677,6 +678,7 @@ enum fuse_opcode { FUSE_COMPOUND =3D 54, =20 FUSE_LOOKUP_HANDLE =3D 55, + FUSE_MKOBJ_HANDLE =3D 56, =20 /* CUSE specific operations */ CUSE_INIT =3D 4096,