public inbox for linux-fsdevel@vger.kernel.org
 help / color / mirror / Atom feed
* fsverity optimzations and speedups
@ 2026-01-19  6:22 Christoph Hellwig
  2026-01-19  6:22 ` [PATCH 1/6] fs,fsverity: reject size changes on fsverity files in setattr_prepare Christoph Hellwig
                   ` (6 more replies)
  0 siblings, 7 replies; 16+ messages in thread
From: Christoph Hellwig @ 2026-01-19  6:22 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	linux-fsdevel, linux-btrfs, linux-ext4, linux-f2fs-devel,
	fsverity

Hi all,

this series has a hodge podge of fsverity enhances that I looked into as
part of the review of the xfs fsverity support series.

The first three patches call fsverity code from VFS code instead of
requiring a lot of boilerplate in the file systems.  The first fixes a
bug in btrfs as part of that, as btrfs was missing a check.  An xfstests
test case for this will be sent separately.

Patch 4 removes the need to have a pointer to the fsverity_info in the
inode structure and uses a global resizable hash table instead.

Patch 5 passes down the struct file in the write path.

Patch 6 optimizes the fsvvrity read path by kicking off readahead for the
fsverity hashes from the data read submission context, which in my
simply testing showed huge benefits for sequential reads using dd.
I haven't been able to get fio to run on a preallocated fio file, but
I expect random read benefits would be significantly better than that
still.

Diffstat:
 fs/attr.c                    |   12 +++
 fs/btrfs/btrfs_inode.h       |    4 -
 fs/btrfs/file.c              |    6 -
 fs/btrfs/inode.c             |   13 ---
 fs/btrfs/verity.c            |   11 +--
 fs/ext4/ext4.h               |    4 -
 fs/ext4/file.c               |    4 -
 fs/ext4/inode.c              |    4 -
 fs/ext4/readpage.c           |    4 +
 fs/ext4/super.c              |    4 -
 fs/ext4/verity.c             |   34 ++++-----
 fs/f2fs/data.c               |    4 +
 fs/f2fs/f2fs.h               |    3 
 fs/f2fs/file.c               |    8 --
 fs/f2fs/inode.c              |    1 
 fs/f2fs/super.c              |    3 
 fs/f2fs/verity.c             |   34 ++++-----
 fs/inode.c                   |    9 ++
 fs/open.c                    |    8 +-
 fs/verity/enable.c           |   39 ++++++-----
 fs/verity/fsverity_private.h |   17 ++--
 fs/verity/open.c             |   90 ++++++++++++++-----------
 fs/verity/read_metadata.c    |   10 +-
 fs/verity/verify.c           |   98 +++++++++++++++++++--------
 include/linux/fsverity.h     |  152 +++++++++----------------------------------
 25 files changed, 262 insertions(+), 314 deletions(-)

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH 1/6] fs,fsverity: reject size changes on fsverity files in setattr_prepare
  2026-01-19  6:22 fsverity optimzations and speedups Christoph Hellwig
@ 2026-01-19  6:22 ` Christoph Hellwig
  2026-01-19  6:22 ` [PATCH 2/6] fs,fsverity: clear out fsverity_info from common code Christoph Hellwig
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 16+ messages in thread
From: Christoph Hellwig @ 2026-01-19  6:22 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	linux-fsdevel, linux-btrfs, linux-ext4, linux-f2fs-devel,
	fsverity

Add the check to reject truncates of fsverity files directly to
setattr_prepare instead of requiring the file system to handle it.
Besides removing boilerplate code, this also fixes the complete lack of
such check in btrfs.

Fixes: 146054090b08 ("btrfs: initial fsverity support")
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
 fs/attr.c                | 12 +++++++++++-
 fs/ext4/inode.c          |  4 ----
 fs/f2fs/file.c           |  4 ----
 fs/verity/open.c         |  8 --------
 include/linux/fsverity.h | 25 -------------------------
 5 files changed, 11 insertions(+), 42 deletions(-)

diff --git a/fs/attr.c b/fs/attr.c
index b9ec6b47bab2..e7d7c6d19fe9 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -169,7 +169,17 @@ int setattr_prepare(struct mnt_idmap *idmap, struct dentry *dentry,
 	 * ATTR_FORCE.
 	 */
 	if (ia_valid & ATTR_SIZE) {
-		int error = inode_newsize_ok(inode, attr->ia_size);
+		int error;
+
+		/*
+		 * Verity files are immutable, so deny truncates.  This isn't
+		 * covered by the open-time check because sys_truncate() takes a
+		 * path, not an open file.
+		 */
+		if (IS_ENABLED(CONFIG_FS_VERITY) && IS_VERITY(inode))
+			return -EPERM;
+
+		error = inode_newsize_ok(inode, attr->ia_size);
 		if (error)
 			return error;
 	}
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 0c466ccbed69..8c2ef98fa530 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -5835,10 +5835,6 @@ int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
 	if (error)
 		return error;
 
-	error = fsverity_prepare_setattr(dentry, attr);
-	if (error)
-		return error;
-
 	if (is_quota_modification(idmap, inode, attr)) {
 		error = dquot_initialize(inode);
 		if (error)
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index d7047ca6b98d..da029fed4e5a 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -1074,10 +1074,6 @@ int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
 	if (err)
 		return err;
 
-	err = fsverity_prepare_setattr(dentry, attr);
-	if (err)
-		return err;
-
 	if (unlikely(IS_IMMUTABLE(inode)))
 		return -EPERM;
 
diff --git a/fs/verity/open.c b/fs/verity/open.c
index 77b1c977af02..2aa5eae5a540 100644
--- a/fs/verity/open.c
+++ b/fs/verity/open.c
@@ -384,14 +384,6 @@ int __fsverity_file_open(struct inode *inode, struct file *filp)
 }
 EXPORT_SYMBOL_GPL(__fsverity_file_open);
 
-int __fsverity_prepare_setattr(struct dentry *dentry, struct iattr *attr)
-{
-	if (attr->ia_valid & ATTR_SIZE)
-		return -EPERM;
-	return 0;
-}
-EXPORT_SYMBOL_GPL(__fsverity_prepare_setattr);
-
 void __fsverity_cleanup_inode(struct inode *inode)
 {
 	struct fsverity_info **vi_addr = fsverity_info_addr(inode);
diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h
index 5bc7280425a7..86fb1708676b 100644
--- a/include/linux/fsverity.h
+++ b/include/linux/fsverity.h
@@ -179,7 +179,6 @@ int fsverity_get_digest(struct inode *inode,
 /* open.c */
 
 int __fsverity_file_open(struct inode *inode, struct file *filp);
-int __fsverity_prepare_setattr(struct dentry *dentry, struct iattr *attr);
 void __fsverity_cleanup_inode(struct inode *inode);
 
 /**
@@ -251,12 +250,6 @@ static inline int __fsverity_file_open(struct inode *inode, struct file *filp)
 	return -EOPNOTSUPP;
 }
 
-static inline int __fsverity_prepare_setattr(struct dentry *dentry,
-					     struct iattr *attr)
-{
-	return -EOPNOTSUPP;
-}
-
 static inline void fsverity_cleanup_inode(struct inode *inode)
 {
 }
@@ -338,22 +331,4 @@ static inline int fsverity_file_open(struct inode *inode, struct file *filp)
 	return 0;
 }
 
-/**
- * fsverity_prepare_setattr() - prepare to change a verity inode's attributes
- * @dentry: dentry through which the inode is being changed
- * @attr: attributes to change
- *
- * Verity files are immutable, so deny truncates.  This isn't covered by the
- * open-time check because sys_truncate() takes a path, not a file descriptor.
- *
- * Return: 0 on success, -errno on failure
- */
-static inline int fsverity_prepare_setattr(struct dentry *dentry,
-					   struct iattr *attr)
-{
-	if (IS_VERITY(d_inode(dentry)))
-		return __fsverity_prepare_setattr(dentry, attr);
-	return 0;
-}
-
 #endif	/* _LINUX_FSVERITY_H */
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 2/6] fs,fsverity: clear out fsverity_info from common code
  2026-01-19  6:22 fsverity optimzations and speedups Christoph Hellwig
  2026-01-19  6:22 ` [PATCH 1/6] fs,fsverity: reject size changes on fsverity files in setattr_prepare Christoph Hellwig
@ 2026-01-19  6:22 ` Christoph Hellwig
  2026-01-19  6:22 ` [PATCH 3/6] fs,fsverity: handle fsverity in generic_file_open Christoph Hellwig
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 16+ messages in thread
From: Christoph Hellwig @ 2026-01-19  6:22 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	linux-fsdevel, linux-btrfs, linux-ext4, linux-f2fs-devel,
	fsverity

Directly remove the fsverity_info from the hash and free it from
clear_inode instead of requiring file systems to handle it.

Signed-off-by: Christoph Hellwig <hch@lst.de>
---
 fs/btrfs/inode.c         | 10 +++-------
 fs/ext4/super.c          |  1 -
 fs/f2fs/inode.c          |  1 -
 fs/inode.c               |  9 +++++++++
 fs/verity/open.c         |  3 +--
 include/linux/fsverity.h | 26 ++------------------------
 6 files changed, 15 insertions(+), 35 deletions(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index a2b5b440637e..67c64efc5099 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -34,7 +34,6 @@
 #include <linux/sched/mm.h>
 #include <linux/iomap.h>
 #include <linux/unaligned.h>
-#include <linux/fsverity.h>
 #include "misc.h"
 #include "ctree.h"
 #include "disk-io.h"
@@ -5571,11 +5570,8 @@ void btrfs_evict_inode(struct inode *inode)
 
 	trace_btrfs_inode_evict(inode);
 
-	if (!root) {
-		fsverity_cleanup_inode(inode);
-		clear_inode(inode);
-		return;
-	}
+	if (!root)
+		goto clear_inode;
 
 	fs_info = inode_to_fs_info(inode);
 	evict_inode_truncate_pages(inode);
@@ -5675,7 +5671,7 @@ void btrfs_evict_inode(struct inode *inode)
 	 * to retry these periodically in the future.
 	 */
 	btrfs_remove_delayed_node(BTRFS_I(inode));
-	fsverity_cleanup_inode(inode);
+clear_inode:
 	clear_inode(inode);
 }
 
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 87205660c5d0..86131f4d8718 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1527,7 +1527,6 @@ void ext4_clear_inode(struct inode *inode)
 		EXT4_I(inode)->jinode = NULL;
 	}
 	fscrypt_put_encryption_info(inode);
-	fsverity_cleanup_inode(inode);
 }
 
 static struct inode *ext4_nfs_get_inode(struct super_block *sb,
diff --git a/fs/f2fs/inode.c b/fs/f2fs/inode.c
index 38b8994bc1b2..ee332b994348 100644
--- a/fs/f2fs/inode.c
+++ b/fs/f2fs/inode.c
@@ -1000,7 +1000,6 @@ void f2fs_evict_inode(struct inode *inode)
 	}
 out_clear:
 	fscrypt_put_encryption_info(inode);
-	fsverity_cleanup_inode(inode);
 	clear_inode(inode);
 }
 
diff --git a/fs/inode.c b/fs/inode.c
index 379f4c19845c..38dbdfbb09ba 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -14,6 +14,7 @@
 #include <linux/cdev.h>
 #include <linux/memblock.h>
 #include <linux/fsnotify.h>
+#include <linux/fsverity.h>
 #include <linux/mount.h>
 #include <linux/posix_acl.h>
 #include <linux/buffer_head.h> /* for inode_has_buffers */
@@ -773,6 +774,14 @@ void dump_mapping(const struct address_space *mapping)
 
 void clear_inode(struct inode *inode)
 {
+	/*
+	 * Only IS_VERITY() inodes can have verity info, so start by checking
+	 * for IS_VERITY() (which is faster than retrieving the pointer to the
+	 * verity info).  This minimizes overhead for non-verity inodes.
+	 */
+	if (IS_ENABLED(CONFIG_FS_VERITY) && IS_VERITY(inode))
+		fsverity_cleanup_inode(inode);
+
 	/*
 	 * We have to cycle the i_pages lock here because reclaim can be in the
 	 * process of removing the last page (in __filemap_remove_folio())
diff --git a/fs/verity/open.c b/fs/verity/open.c
index 2aa5eae5a540..090cb77326ee 100644
--- a/fs/verity/open.c
+++ b/fs/verity/open.c
@@ -384,14 +384,13 @@ int __fsverity_file_open(struct inode *inode, struct file *filp)
 }
 EXPORT_SYMBOL_GPL(__fsverity_file_open);
 
-void __fsverity_cleanup_inode(struct inode *inode)
+void fsverity_cleanup_inode(struct inode *inode)
 {
 	struct fsverity_info **vi_addr = fsverity_info_addr(inode);
 
 	fsverity_free_info(*vi_addr);
 	*vi_addr = NULL;
 }
-EXPORT_SYMBOL_GPL(__fsverity_cleanup_inode);
 
 void __init fsverity_init_info_cache(void)
 {
diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h
index 86fb1708676b..b7bf2401c574 100644
--- a/include/linux/fsverity.h
+++ b/include/linux/fsverity.h
@@ -130,6 +130,8 @@ struct fsverity_operations {
 				       u64 pos, unsigned int size);
 };
 
+void fsverity_cleanup_inode(struct inode *inode);
+
 #ifdef CONFIG_FS_VERITY
 
 /*
@@ -179,26 +181,6 @@ int fsverity_get_digest(struct inode *inode,
 /* open.c */
 
 int __fsverity_file_open(struct inode *inode, struct file *filp);
-void __fsverity_cleanup_inode(struct inode *inode);
-
-/**
- * fsverity_cleanup_inode() - free the inode's verity info, if present
- * @inode: an inode being evicted
- *
- * Filesystems must call this on inode eviction to free the inode's verity info.
- */
-static inline void fsverity_cleanup_inode(struct inode *inode)
-{
-	/*
-	 * Only IS_VERITY() inodes can have verity info, so start by checking
-	 * for IS_VERITY() (which is faster than retrieving the pointer to the
-	 * verity info).  This minimizes overhead for non-verity inodes.
-	 */
-	if (IS_VERITY(inode))
-		__fsverity_cleanup_inode(inode);
-	else
-		VFS_WARN_ON_ONCE(*fsverity_info_addr(inode) != NULL);
-}
 
 /* read_metadata.c */
 
@@ -250,10 +232,6 @@ static inline int __fsverity_file_open(struct inode *inode, struct file *filp)
 	return -EOPNOTSUPP;
 }
 
-static inline void fsverity_cleanup_inode(struct inode *inode)
-{
-}
-
 /* read_metadata.c */
 
 static inline int fsverity_ioctl_read_metadata(struct file *filp,
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 3/6] fs,fsverity: handle fsverity in generic_file_open
  2026-01-19  6:22 fsverity optimzations and speedups Christoph Hellwig
  2026-01-19  6:22 ` [PATCH 1/6] fs,fsverity: reject size changes on fsverity files in setattr_prepare Christoph Hellwig
  2026-01-19  6:22 ` [PATCH 2/6] fs,fsverity: clear out fsverity_info from common code Christoph Hellwig
@ 2026-01-19  6:22 ` Christoph Hellwig
  2026-01-19  9:05   ` Jan Kara
  2026-01-19 10:02   ` Christian Brauner
  2026-01-19  6:22 ` [PATCH 4/6] fsverity: use a hashtable to find the fsverity_info Christoph Hellwig
                   ` (3 subsequent siblings)
  6 siblings, 2 replies; 16+ messages in thread
From: Christoph Hellwig @ 2026-01-19  6:22 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	linux-fsdevel, linux-btrfs, linux-ext4, linux-f2fs-devel,
	fsverity

Call into fsverity_file_open from generic_file_open instead of requiring
the file system to handle it explicitly.

Signed-off-by: Christoph Hellwig <hch@lst.de>
---
 fs/btrfs/file.c          |  6 ------
 fs/ext4/file.c           |  4 ----
 fs/f2fs/file.c           |  4 ----
 fs/open.c                |  8 +++++++-
 fs/verity/open.c         | 10 ++++++++--
 include/linux/fsverity.h | 32 +-------------------------------
 6 files changed, 16 insertions(+), 48 deletions(-)

diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index 1abc7ed2990e..4b3a31b2b52e 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -3808,16 +3808,10 @@ static loff_t btrfs_file_llseek(struct file *file, loff_t offset, int whence)
 
 static int btrfs_file_open(struct inode *inode, struct file *filp)
 {
-	int ret;
-
 	if (unlikely(btrfs_is_shutdown(inode_to_fs_info(inode))))
 		return -EIO;
 
 	filp->f_mode |= FMODE_NOWAIT | FMODE_CAN_ODIRECT;
-
-	ret = fsverity_file_open(inode, filp);
-	if (ret)
-		return ret;
 	return generic_file_open(inode, filp);
 }
 
diff --git a/fs/ext4/file.c b/fs/ext4/file.c
index 7a8b30932189..a7dc8c10273e 100644
--- a/fs/ext4/file.c
+++ b/fs/ext4/file.c
@@ -906,10 +906,6 @@ static int ext4_file_open(struct inode *inode, struct file *filp)
 	if (ret)
 		return ret;
 
-	ret = fsverity_file_open(inode, filp);
-	if (ret)
-		return ret;
-
 	/*
 	 * Set up the jbd2_inode if we are opening the inode for
 	 * writing and the journal is present
diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index da029fed4e5a..f1510ab657b6 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -624,10 +624,6 @@ static int f2fs_file_open(struct inode *inode, struct file *filp)
 	if (!f2fs_is_compress_backend_ready(inode))
 		return -EOPNOTSUPP;
 
-	err = fsverity_file_open(inode, filp);
-	if (err)
-		return err;
-
 	filp->f_mode |= FMODE_NOWAIT;
 	filp->f_mode |= FMODE_CAN_ODIRECT;
 
diff --git a/fs/open.c b/fs/open.c
index f328622061c5..dea93bab8795 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -10,6 +10,7 @@
 #include <linux/file.h>
 #include <linux/fdtable.h>
 #include <linux/fsnotify.h>
+#include <linux/fsverity.h>
 #include <linux/module.h>
 #include <linux/tty.h>
 #include <linux/namei.h>
@@ -1604,10 +1605,15 @@ SYSCALL_DEFINE0(vhangup)
  * the caller didn't specify O_LARGEFILE.  On 64bit systems we force
  * on this flag in sys_open.
  */
-int generic_file_open(struct inode * inode, struct file * filp)
+int generic_file_open(struct inode *inode, struct file *filp)
 {
 	if (!(filp->f_flags & O_LARGEFILE) && i_size_read(inode) > MAX_NON_LFS)
 		return -EOVERFLOW;
+	if (IS_ENABLED(CONFIG_FS_VERITY) && IS_VERITY(inode)) {
+		if (filp->f_mode & FMODE_WRITE)
+			return -EPERM;
+		return fsverity_file_open(inode, filp);
+	}
 	return 0;
 }
 
diff --git a/fs/verity/open.c b/fs/verity/open.c
index 090cb77326ee..8ed915be9c91 100644
--- a/fs/verity/open.c
+++ b/fs/verity/open.c
@@ -376,13 +376,19 @@ static int ensure_verity_info(struct inode *inode)
 	return err;
 }
 
-int __fsverity_file_open(struct inode *inode, struct file *filp)
+/*
+ * When opening a verity file, deny the open if it is for writing.  Otherwise,
+ * set up the inode's verity info if not already done.
+ *
+ * When combined with fscrypt, this must be called after fscrypt_file_open().
+ * Otherwise, we won't have the key set up to decrypt the verity metadata.
+ */
+int fsverity_file_open(struct inode *inode, struct file *filp)
 {
 	if (filp->f_mode & FMODE_WRITE)
 		return -EPERM;
 	return ensure_verity_info(inode);
 }
-EXPORT_SYMBOL_GPL(__fsverity_file_open);
 
 void fsverity_cleanup_inode(struct inode *inode)
 {
diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h
index b7bf2401c574..4980ea55cdaa 100644
--- a/include/linux/fsverity.h
+++ b/include/linux/fsverity.h
@@ -130,6 +130,7 @@ struct fsverity_operations {
 				       u64 pos, unsigned int size);
 };
 
+int fsverity_file_open(struct inode *inode, struct file *filp);
 void fsverity_cleanup_inode(struct inode *inode);
 
 #ifdef CONFIG_FS_VERITY
@@ -178,10 +179,6 @@ int fsverity_get_digest(struct inode *inode,
 			u8 raw_digest[FS_VERITY_MAX_DIGEST_SIZE],
 			u8 *alg, enum hash_algo *halg);
 
-/* open.c */
-
-int __fsverity_file_open(struct inode *inode, struct file *filp);
-
 /* read_metadata.c */
 
 int fsverity_ioctl_read_metadata(struct file *filp, const void __user *uarg);
@@ -225,13 +222,6 @@ static inline int fsverity_get_digest(struct inode *inode,
 	return 0;
 }
 
-/* open.c */
-
-static inline int __fsverity_file_open(struct inode *inode, struct file *filp)
-{
-	return -EOPNOTSUPP;
-}
-
 /* read_metadata.c */
 
 static inline int fsverity_ioctl_read_metadata(struct file *filp,
@@ -289,24 +279,4 @@ static inline bool fsverity_active(const struct inode *inode)
 	return fsverity_get_info(inode) != NULL;
 }
 
-/**
- * fsverity_file_open() - prepare to open a verity file
- * @inode: the inode being opened
- * @filp: the struct file being set up
- *
- * When opening a verity file, deny the open if it is for writing.  Otherwise,
- * set up the inode's verity info if not already done.
- *
- * When combined with fscrypt, this must be called after fscrypt_file_open().
- * Otherwise, we won't have the key set up to decrypt the verity metadata.
- *
- * Return: 0 on success, -errno on failure
- */
-static inline int fsverity_file_open(struct inode *inode, struct file *filp)
-{
-	if (IS_VERITY(inode))
-		return __fsverity_file_open(inode, filp);
-	return 0;
-}
-
 #endif	/* _LINUX_FSVERITY_H */
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 4/6] fsverity: use a hashtable to find the fsverity_info
  2026-01-19  6:22 fsverity optimzations and speedups Christoph Hellwig
                   ` (2 preceding siblings ...)
  2026-01-19  6:22 ` [PATCH 3/6] fs,fsverity: handle fsverity in generic_file_open Christoph Hellwig
@ 2026-01-19  6:22 ` Christoph Hellwig
  2026-01-19  9:21   ` Jan Kara
  2026-01-19 19:05   ` Eric Biggers
  2026-01-19  6:22 ` [PATCH 5/6] fsverity: pass struct file to ->write_merkle_tree_block Christoph Hellwig
                   ` (2 subsequent siblings)
  6 siblings, 2 replies; 16+ messages in thread
From: Christoph Hellwig @ 2026-01-19  6:22 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	linux-fsdevel, linux-btrfs, linux-ext4, linux-f2fs-devel,
	fsverity

Use the kernel's resizable hash table to find the fsverity_info.  This
way file systems that want to support fsverity don't have to bloat
every inode in the system with an extra pointer.  The tradeoff is that
looking up the fsverity_info is a bit more expensive now, but the main
operations are still dominated by I/O and hashing overhead.

Signed-off-by: Christoph Hellwig <hch@lst.de>
---
 fs/btrfs/btrfs_inode.h       |  4 --
 fs/btrfs/inode.c             |  3 --
 fs/btrfs/verity.c            |  2 -
 fs/ext4/ext4.h               |  4 --
 fs/ext4/super.c              |  3 --
 fs/ext4/verity.c             |  2 -
 fs/f2fs/f2fs.h               |  3 --
 fs/f2fs/super.c              |  3 --
 fs/f2fs/verity.c             |  2 -
 fs/verity/enable.c           | 30 +++++++++------
 fs/verity/fsverity_private.h | 17 ++++----
 fs/verity/open.c             | 75 ++++++++++++++++++++++--------------
 fs/verity/verify.c           |  6 +--
 include/linux/fsverity.h     | 47 ++++------------------
 14 files changed, 83 insertions(+), 118 deletions(-)

diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h
index 73602ee8de3f..55c272fe5d92 100644
--- a/fs/btrfs/btrfs_inode.h
+++ b/fs/btrfs/btrfs_inode.h
@@ -339,10 +339,6 @@ struct btrfs_inode {
 
 	struct rw_semaphore i_mmap_lock;
 
-#ifdef CONFIG_FS_VERITY
-	struct fsverity_info *i_verity_info;
-#endif
-
 	struct inode vfs_inode;
 };
 
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 67c64efc5099..93b2ce75fb06 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -8097,9 +8097,6 @@ static void init_once(void *foo)
 	struct btrfs_inode *ei = foo;
 
 	inode_init_once(&ei->vfs_inode);
-#ifdef CONFIG_FS_VERITY
-	ei->i_verity_info = NULL;
-#endif
 }
 
 void __cold btrfs_destroy_cachep(void)
diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c
index a2ac3fb68bc8..e79e67280a4b 100644
--- a/fs/btrfs/verity.c
+++ b/fs/btrfs/verity.c
@@ -796,8 +796,6 @@ static int btrfs_write_merkle_tree_block(struct inode *inode, const void *buf,
 }
 
 const struct fsverity_operations btrfs_verityops = {
-	.inode_info_offs         = (int)offsetof(struct btrfs_inode, i_verity_info) -
-				   (int)offsetof(struct btrfs_inode, vfs_inode),
 	.begin_enable_verity     = btrfs_begin_enable_verity,
 	.end_enable_verity       = btrfs_end_enable_verity,
 	.get_verity_descriptor   = btrfs_get_verity_descriptor,
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 56112f201cac..60c549bc894e 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -1205,10 +1205,6 @@ struct ext4_inode_info {
 #ifdef CONFIG_FS_ENCRYPTION
 	struct fscrypt_inode_info *i_crypt_info;
 #endif
-
-#ifdef CONFIG_FS_VERITY
-	struct fsverity_info *i_verity_info;
-#endif
 };
 
 /*
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 86131f4d8718..1fb0c90c7a4b 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1484,9 +1484,6 @@ static void init_once(void *foo)
 #ifdef CONFIG_FS_ENCRYPTION
 	ei->i_crypt_info = NULL;
 #endif
-#ifdef CONFIG_FS_VERITY
-	ei->i_verity_info = NULL;
-#endif
 }
 
 static int __init init_inodecache(void)
diff --git a/fs/ext4/verity.c b/fs/ext4/verity.c
index 415d9c4d8a32..7a980a8059bd 100644
--- a/fs/ext4/verity.c
+++ b/fs/ext4/verity.c
@@ -389,8 +389,6 @@ static int ext4_write_merkle_tree_block(struct inode *inode, const void *buf,
 }
 
 const struct fsverity_operations ext4_verityops = {
-	.inode_info_offs	= (int)offsetof(struct ext4_inode_info, i_verity_info) -
-				  (int)offsetof(struct ext4_inode_info, vfs_inode),
 	.begin_enable_verity	= ext4_begin_enable_verity,
 	.end_enable_verity	= ext4_end_enable_verity,
 	.get_verity_descriptor	= ext4_get_verity_descriptor,
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index 20edbb99b814..6b0933de0e2e 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -974,9 +974,6 @@ struct f2fs_inode_info {
 #ifdef CONFIG_FS_ENCRYPTION
 	struct fscrypt_inode_info *i_crypt_info; /* filesystem encryption info */
 #endif
-#ifdef CONFIG_FS_VERITY
-	struct fsverity_info *i_verity_info; /* filesystem verity info */
-#endif
 };
 
 static inline void get_read_extent_info(struct extent_info *ext,
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index c4c225e09dc4..cd00d030edda 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -504,9 +504,6 @@ static void init_once(void *foo)
 #ifdef CONFIG_FS_ENCRYPTION
 	fi->i_crypt_info = NULL;
 #endif
-#ifdef CONFIG_FS_VERITY
-	fi->i_verity_info = NULL;
-#endif
 }
 
 #ifdef CONFIG_QUOTA
diff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c
index 05b935b55216..c30b70fbcc0d 100644
--- a/fs/f2fs/verity.c
+++ b/fs/f2fs/verity.c
@@ -287,8 +287,6 @@ static int f2fs_write_merkle_tree_block(struct inode *inode, const void *buf,
 }
 
 const struct fsverity_operations f2fs_verityops = {
-	.inode_info_offs	= (int)offsetof(struct f2fs_inode_info, i_verity_info) -
-				  (int)offsetof(struct f2fs_inode_info, vfs_inode),
 	.begin_enable_verity	= f2fs_begin_enable_verity,
 	.end_enable_verity	= f2fs_end_enable_verity,
 	.get_verity_descriptor	= f2fs_get_verity_descriptor,
diff --git a/fs/verity/enable.c b/fs/verity/enable.c
index 95ec42b84797..91cada0d455c 100644
--- a/fs/verity/enable.c
+++ b/fs/verity/enable.c
@@ -264,9 +264,24 @@ static int enable_verity(struct file *filp,
 		goto rollback;
 	}
 
+	/*
+	 * Add the fsverity_info into the hash table before finishing the
+	 * initialization.  This ensures we don't have to undo the enabling when
+	 * memory allocation for the hash table fails.  This is safe because
+	 * looking up the fsverity_info always first checks the S_VERITY flag on
+	 * the inode, which will only be set at the very end of the
+	 * ->end_enable_verity method.
+	 */
+	err = fsverity_set_info(vi);
+	if (err)
+		goto rollback;
+
 	/*
 	 * Tell the filesystem to finish enabling verity on the file.
-	 * Serialized with ->begin_enable_verity() by the inode lock.
+	 * Serialized with ->begin_enable_verity() by the inode lock.  The file
+	 * system needs to set the S_VERITY flag on the inode at the very end of
+	 * the method, at which point the fsverity information can be accessed
+	 * by other threads.
 	 */
 	inode_lock(inode);
 	err = vops->end_enable_verity(filp, desc, desc_size, params.tree_size);
@@ -274,19 +289,10 @@ static int enable_verity(struct file *filp,
 	if (err) {
 		fsverity_err(inode, "%ps() failed with err %d",
 			     vops->end_enable_verity, err);
-		fsverity_free_info(vi);
+		fsverity_remove_info(vi);
 	} else if (WARN_ON_ONCE(!IS_VERITY(inode))) {
+		fsverity_remove_info(vi);
 		err = -EINVAL;
-		fsverity_free_info(vi);
-	} else {
-		/* Successfully enabled verity */
-
-		/*
-		 * Readers can start using the inode's verity info immediately,
-		 * so it can't be rolled back once set.  So don't set it until
-		 * just after the filesystem has successfully enabled verity.
-		 */
-		fsverity_set_info(inode, vi);
 	}
 out:
 	kfree(params.hashstate);
diff --git a/fs/verity/fsverity_private.h b/fs/verity/fsverity_private.h
index dd20b138d452..68a85b6202b5 100644
--- a/fs/verity/fsverity_private.h
+++ b/fs/verity/fsverity_private.h
@@ -11,6 +11,7 @@
 #define pr_fmt(fmt) "fs-verity: " fmt
 
 #include <linux/fsverity.h>
+#include <linux/rhashtable.h>
 
 /*
  * Implementation limit: maximum depth of the Merkle tree.  For now 8 is plenty;
@@ -63,13 +64,14 @@ struct merkle_tree_params {
  * fsverity_info - cached verity metadata for an inode
  *
  * When a verity file is first opened, an instance of this struct is allocated
- * and a pointer to it is stored in the file's in-memory inode.  It remains
- * until the inode is evicted.  It caches information about the Merkle tree
- * that's needed to efficiently verify data read from the file.  It also caches
- * the file digest.  The Merkle tree pages themselves are not cached here, but
- * the filesystem may cache them.
+ * and a pointer to it is stored in the global hash table, indexed by the inode
+ * pointer value.  It remains alive until the inode is evicted.  It caches
+ * information about the Merkle tree that's needed to efficiently verify data
+ * read from the file.  It also caches the file digest.  The Merkle tree pages
+ * themselves are not cached here, but the filesystem may cache them.
  */
 struct fsverity_info {
+	struct rhash_head rhash_head;
 	struct merkle_tree_params tree_params;
 	u8 root_hash[FS_VERITY_MAX_DIGEST_SIZE];
 	u8 file_digest[FS_VERITY_MAX_DIGEST_SIZE];
@@ -127,9 +129,8 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params,
 struct fsverity_info *fsverity_create_info(const struct inode *inode,
 					   struct fsverity_descriptor *desc);
 
-void fsverity_set_info(struct inode *inode, struct fsverity_info *vi);
-
-void fsverity_free_info(struct fsverity_info *vi);
+int fsverity_set_info(struct fsverity_info *vi);
+void fsverity_remove_info(struct fsverity_info *vi);
 
 int fsverity_get_descriptor(struct inode *inode,
 			    struct fsverity_descriptor **desc_ret);
diff --git a/fs/verity/open.c b/fs/verity/open.c
index 8ed915be9c91..17da6c0b2015 100644
--- a/fs/verity/open.c
+++ b/fs/verity/open.c
@@ -12,6 +12,14 @@
 #include <linux/slab.h>
 
 static struct kmem_cache *fsverity_info_cachep;
+static struct rhashtable fsverity_info_hash;
+
+static const struct rhashtable_params fsverity_info_hash_params = {
+	.key_len		= sizeof(struct inode *),
+	.key_offset		= offsetof(struct fsverity_info, inode),
+	.head_offset		= offsetof(struct fsverity_info, rhash_head),
+	.automatic_shrinking	= true,
+};
 
 /**
  * fsverity_init_merkle_tree_params() - initialize Merkle tree parameters
@@ -170,6 +178,13 @@ static void compute_file_digest(const struct fsverity_hash_alg *hash_alg,
 	desc->sig_size = sig_size;
 }
 
+static void fsverity_free_info(struct fsverity_info *vi)
+{
+	kfree(vi->tree_params.hashstate);
+	kvfree(vi->hash_block_verified);
+	kmem_cache_free(fsverity_info_cachep, vi);
+}
+
 /*
  * Create a new fsverity_info from the given fsverity_descriptor (with optional
  * appended builtin signature), and check the signature if present.  The
@@ -241,33 +256,18 @@ struct fsverity_info *fsverity_create_info(const struct inode *inode,
 	return ERR_PTR(err);
 }
 
-void fsverity_set_info(struct inode *inode, struct fsverity_info *vi)
+int fsverity_set_info(struct fsverity_info *vi)
 {
-	/*
-	 * Multiple tasks may race to set the inode's verity info pointer, so
-	 * use cmpxchg_release().  This pairs with the smp_load_acquire() in
-	 * fsverity_get_info().  I.e., publish the pointer with a RELEASE
-	 * barrier so that other tasks can ACQUIRE it.
-	 */
-	if (cmpxchg_release(fsverity_info_addr(inode), NULL, vi) != NULL) {
-		/* Lost the race, so free the verity info we allocated. */
-		fsverity_free_info(vi);
-		/*
-		 * Afterwards, the caller may access the inode's verity info
-		 * directly, so make sure to ACQUIRE the winning verity info.
-		 */
-		(void)fsverity_get_info(inode);
-	}
+	return rhashtable_lookup_insert_fast(&fsverity_info_hash,
+			&vi->rhash_head, fsverity_info_hash_params);
 }
 
-void fsverity_free_info(struct fsverity_info *vi)
+struct fsverity_info *__fsverity_get_info(const struct inode *inode)
 {
-	if (!vi)
-		return;
-	kfree(vi->tree_params.hashstate);
-	kvfree(vi->hash_block_verified);
-	kmem_cache_free(fsverity_info_cachep, vi);
+	return rhashtable_lookup_fast(&fsverity_info_hash, &inode,
+			fsverity_info_hash_params);
 }
+EXPORT_SYMBOL_GPL(__fsverity_get_info);
 
 static bool validate_fsverity_descriptor(struct inode *inode,
 					 const struct fsverity_descriptor *desc,
@@ -352,7 +352,7 @@ int fsverity_get_descriptor(struct inode *inode,
 
 static int ensure_verity_info(struct inode *inode)
 {
-	struct fsverity_info *vi = fsverity_get_info(inode);
+	struct fsverity_info *vi = fsverity_get_info(inode), *found;
 	struct fsverity_descriptor *desc;
 	int err;
 
@@ -369,8 +369,18 @@ static int ensure_verity_info(struct inode *inode)
 		goto out_free_desc;
 	}
 
-	fsverity_set_info(inode, vi);
-	err = 0;
+	/*
+	 * Multiple tasks may race to set the inode's verity info, in which case
+	 * we might find an existing fsverity_info in the hash table.
+	 */
+	found = rhashtable_lookup_get_insert_fast(&fsverity_info_hash,
+			&vi->rhash_head, fsverity_info_hash_params);
+	if (found) {
+		fsverity_free_info(vi);
+		if (IS_ERR(found))
+			err = PTR_ERR(found);
+	}
+
 out_free_desc:
 	kfree(desc);
 	return err;
@@ -390,16 +400,25 @@ int fsverity_file_open(struct inode *inode, struct file *filp)
 	return ensure_verity_info(inode);
 }
 
+void fsverity_remove_info(struct fsverity_info *vi)
+{
+	rhashtable_remove_fast(&fsverity_info_hash, &vi->rhash_head,
+			fsverity_info_hash_params);
+	fsverity_free_info(vi);
+}
+
 void fsverity_cleanup_inode(struct inode *inode)
 {
-	struct fsverity_info **vi_addr = fsverity_info_addr(inode);
+	struct fsverity_info *vi = fsverity_get_info(inode);
 
-	fsverity_free_info(*vi_addr);
-	*vi_addr = NULL;
+	if (vi)
+		fsverity_remove_info(vi);
 }
 
 void __init fsverity_init_info_cache(void)
 {
+	if (rhashtable_init(&fsverity_info_hash, &fsverity_info_hash_params))
+		panic("failed to initialize fsverity hash\n");
 	fsverity_info_cachep = KMEM_CACHE_USERCOPY(
 					fsverity_info,
 					SLAB_RECLAIM_ACCOUNT | SLAB_PANIC,
diff --git a/fs/verity/verify.c b/fs/verity/verify.c
index 86067c8b40cf..3cc81658e4f3 100644
--- a/fs/verity/verify.c
+++ b/fs/verity/verify.c
@@ -275,13 +275,11 @@ fsverity_init_verification_context(struct fsverity_verification_context *ctx,
 				   struct inode *inode,
 				   unsigned long max_ra_pages)
 {
-	struct fsverity_info *vi = *fsverity_info_addr(inode);
-
 	ctx->inode = inode;
-	ctx->vi = vi;
+	ctx->vi = fsverity_get_info(inode);
 	ctx->max_ra_pages = max_ra_pages;
 	ctx->num_pending = 0;
-	if (vi->tree_params.hash_alg->algo_id == HASH_ALGO_SHA256 &&
+	if (ctx->vi->tree_params.hash_alg->algo_id == HASH_ALGO_SHA256 &&
 	    sha256_finup_2x_is_optimized())
 		ctx->max_pending = 2;
 	else
diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h
index 4980ea55cdaa..27abf5b867bb 100644
--- a/include/linux/fsverity.h
+++ b/include/linux/fsverity.h
@@ -30,13 +30,6 @@ struct fsverity_info;
 
 /* Verity operations for filesystems */
 struct fsverity_operations {
-	/**
-	 * The offset of the pointer to struct fsverity_info in the
-	 * filesystem-specific part of the inode, relative to the beginning of
-	 * the common part of the inode (the 'struct inode').
-	 */
-	ptrdiff_t inode_info_offs;
-
 	/**
 	 * Begin enabling verity on the given file.
 	 *
@@ -132,42 +125,21 @@ struct fsverity_operations {
 
 int fsverity_file_open(struct inode *inode, struct file *filp);
 void fsverity_cleanup_inode(struct inode *inode);
-
-#ifdef CONFIG_FS_VERITY
-
-/*
- * Returns the address of the verity info pointer within the filesystem-specific
- * part of the inode.  (To save memory on filesystems that don't support
- * fsverity, a field in 'struct inode' itself is no longer used.)
- */
-static inline struct fsverity_info **
-fsverity_info_addr(const struct inode *inode)
-{
-	VFS_WARN_ON_ONCE(inode->i_sb->s_vop->inode_info_offs == 0);
-	return (void *)inode + inode->i_sb->s_vop->inode_info_offs;
-}
+struct fsverity_info *__fsverity_get_info(const struct inode *inode);
 
 static inline struct fsverity_info *fsverity_get_info(const struct inode *inode)
 {
 	/*
-	 * Since this function can be called on inodes belonging to filesystems
-	 * that don't support fsverity at all, and fsverity_info_addr() doesn't
-	 * work on such filesystems, we have to start with an IS_VERITY() check.
-	 * Checking IS_VERITY() here is also useful to minimize the overhead of
-	 * fsverity_active() on non-verity files.
+	 * Check IS_VERITY() to minimize the overhead of fsverity_active() on
+	 * non-verity files, including file systems that don't support fsverity
+	 * at all.
 	 */
-	if (!IS_VERITY(inode))
+	if (!IS_ENABLED(CONFIG_FS_VERITY) || !IS_VERITY(inode))
 		return NULL;
-
-	/*
-	 * Pairs with the cmpxchg_release() in fsverity_set_info().  I.e.,
-	 * another task may publish the inode's verity info concurrently,
-	 * executing a RELEASE barrier.  Use smp_load_acquire() here to safely
-	 * ACQUIRE the memory the other task published.
-	 */
-	return smp_load_acquire(fsverity_info_addr(inode));
+	return __fsverity_get_info(inode);
 }
 
+#ifdef CONFIG_FS_VERITY
 /* enable.c */
 
 int fsverity_ioctl_enable(struct file *filp, const void __user *arg);
@@ -191,11 +163,6 @@ void fsverity_enqueue_verify_work(struct work_struct *work);
 
 #else /* !CONFIG_FS_VERITY */
 
-static inline struct fsverity_info *fsverity_get_info(const struct inode *inode)
-{
-	return NULL;
-}
-
 /* enable.c */
 
 static inline int fsverity_ioctl_enable(struct file *filp,
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 5/6] fsverity: pass struct file to ->write_merkle_tree_block
  2026-01-19  6:22 fsverity optimzations and speedups Christoph Hellwig
                   ` (3 preceding siblings ...)
  2026-01-19  6:22 ` [PATCH 4/6] fsverity: use a hashtable to find the fsverity_info Christoph Hellwig
@ 2026-01-19  6:22 ` Christoph Hellwig
  2026-01-19  6:22 ` [PATCH 6/6] fsverity: kick off hash readahead at data I/O submission time Christoph Hellwig
  2026-01-19  9:37 ` fsverity optimzations and speedups Christian Brauner
  6 siblings, 0 replies; 16+ messages in thread
From: Christoph Hellwig @ 2026-01-19  6:22 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	linux-fsdevel, linux-btrfs, linux-ext4, linux-f2fs-devel,
	fsverity

This will make an iomap implementation of the method easier.

Signed-off-by: Christoph Hellwig <hch@lst.de>
---
 fs/btrfs/verity.c        | 5 +++--
 fs/ext4/verity.c         | 6 +++---
 fs/f2fs/verity.c         | 6 +++---
 fs/verity/enable.c       | 9 +++++----
 include/linux/fsverity.h | 4 ++--
 5 files changed, 16 insertions(+), 14 deletions(-)

diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c
index e79e67280a4b..8a4426f8b5fb 100644
--- a/fs/btrfs/verity.c
+++ b/fs/btrfs/verity.c
@@ -774,16 +774,17 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode,
 /*
  * fsverity op that writes a Merkle tree block into the btree.
  *
- * @inode:	inode to write a Merkle tree block for
+ * @file:	file to write a Merkle tree block for
  * @buf:	Merkle tree block to write
  * @pos:	the position of the block in the Merkle tree (in bytes)
  * @size:	the Merkle tree block size (in bytes)
  *
  * Returns 0 on success or negative error code on failure
  */
-static int btrfs_write_merkle_tree_block(struct inode *inode, const void *buf,
+static int btrfs_write_merkle_tree_block(struct file *file, const void *buf,
 					 u64 pos, unsigned int size)
 {
+	struct inode *inode = file_inode(file);
 	loff_t merkle_pos = merkle_file_pos(inode);
 
 	if (merkle_pos < 0)
diff --git a/fs/ext4/verity.c b/fs/ext4/verity.c
index 7a980a8059bd..2cd6fe2fbf64 100644
--- a/fs/ext4/verity.c
+++ b/fs/ext4/verity.c
@@ -380,12 +380,12 @@ static struct page *ext4_read_merkle_tree_page(struct inode *inode,
 	return folio_file_page(folio, index);
 }
 
-static int ext4_write_merkle_tree_block(struct inode *inode, const void *buf,
+static int ext4_write_merkle_tree_block(struct file *file, const void *buf,
 					u64 pos, unsigned int size)
 {
-	pos += ext4_verity_metadata_pos(inode);
+	pos += ext4_verity_metadata_pos(file_inode(file));
 
-	return pagecache_write(inode, buf, size, pos);
+	return pagecache_write(file_inode(file), buf, size, pos);
 }
 
 const struct fsverity_operations ext4_verityops = {
diff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c
index c30b70fbcc0d..4326fd72cc72 100644
--- a/fs/f2fs/verity.c
+++ b/fs/f2fs/verity.c
@@ -278,12 +278,12 @@ static struct page *f2fs_read_merkle_tree_page(struct inode *inode,
 	return folio_file_page(folio, index);
 }
 
-static int f2fs_write_merkle_tree_block(struct inode *inode, const void *buf,
+static int f2fs_write_merkle_tree_block(struct file *file, const void *buf,
 					u64 pos, unsigned int size)
 {
-	pos += f2fs_verity_metadata_pos(inode);
+	pos += f2fs_verity_metadata_pos(file_inode(file));
 
-	return pagecache_write(inode, buf, size, pos);
+	return pagecache_write(file_inode(file), buf, size, pos);
 }
 
 const struct fsverity_operations f2fs_verityops = {
diff --git a/fs/verity/enable.c b/fs/verity/enable.c
index 91cada0d455c..2a285b04e8d4 100644
--- a/fs/verity/enable.c
+++ b/fs/verity/enable.c
@@ -41,14 +41,15 @@ static int hash_one_block(const struct merkle_tree_params *params,
 	return 0;
 }
 
-static int write_merkle_tree_block(struct inode *inode, const u8 *buf,
+static int write_merkle_tree_block(struct file *file, const u8 *buf,
 				   unsigned long index,
 				   const struct merkle_tree_params *params)
 {
+	struct inode *inode = file_inode(file);
 	u64 pos = (u64)index << params->log_blocksize;
 	int err;
 
-	err = inode->i_sb->s_vop->write_merkle_tree_block(inode, buf, pos,
+	err = inode->i_sb->s_vop->write_merkle_tree_block(file, buf, pos,
 							  params->block_size);
 	if (err)
 		fsverity_err(inode, "Error %d writing Merkle tree block %lu",
@@ -135,7 +136,7 @@ static int build_merkle_tree(struct file *filp,
 			err = hash_one_block(params, &buffers[level]);
 			if (err)
 				goto out;
-			err = write_merkle_tree_block(inode,
+			err = write_merkle_tree_block(filp,
 						      buffers[level].data,
 						      level_offset[level],
 						      params);
@@ -155,7 +156,7 @@ static int build_merkle_tree(struct file *filp,
 			err = hash_one_block(params, &buffers[level]);
 			if (err)
 				goto out;
-			err = write_merkle_tree_block(inode,
+			err = write_merkle_tree_block(filp,
 						      buffers[level].data,
 						      level_offset[level],
 						      params);
diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h
index 27abf5b867bb..deb6b2303d64 100644
--- a/include/linux/fsverity.h
+++ b/include/linux/fsverity.h
@@ -109,7 +109,7 @@ struct fsverity_operations {
 	/**
 	 * Write a Merkle tree block to the given inode.
 	 *
-	 * @inode: the inode for which the Merkle tree is being built
+	 * @file: the file for which the Merkle tree is being built
 	 * @buf: the Merkle tree block to write
 	 * @pos: the position of the block in the Merkle tree (in bytes)
 	 * @size: the Merkle tree block size (in bytes)
@@ -119,7 +119,7 @@ struct fsverity_operations {
 	 *
 	 * Return: 0 on success, -errno on failure
 	 */
-	int (*write_merkle_tree_block)(struct inode *inode, const void *buf,
+	int (*write_merkle_tree_block)(struct file *file, const void *buf,
 				       u64 pos, unsigned int size);
 };
 
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 6/6] fsverity: kick off hash readahead at data I/O submission time
  2026-01-19  6:22 fsverity optimzations and speedups Christoph Hellwig
                   ` (4 preceding siblings ...)
  2026-01-19  6:22 ` [PATCH 5/6] fsverity: pass struct file to ->write_merkle_tree_block Christoph Hellwig
@ 2026-01-19  6:22 ` Christoph Hellwig
  2026-01-19  9:37 ` fsverity optimzations and speedups Christian Brauner
  6 siblings, 0 replies; 16+ messages in thread
From: Christoph Hellwig @ 2026-01-19  6:22 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	linux-fsdevel, linux-btrfs, linux-ext4, linux-f2fs-devel,
	fsverity

Currently all reads of the fsverity hashes is kicked off from the data
I/O completion handler, leading to needlessly dependent I/O.  This is
worked around a bit by performing readahead on the level 0 nodes, but
still fairly ineffective.

Switch to a model where the ->read_folio and ->readahead methods instead
kick off explicit readahead of the fsverity hashed so they are usually
available at I/O completion time.

For 64k sequential reads on my test VM this improves read performance
from 2.4GB/s - 2.6GB/s to 3.5GB/s - 3.9GB/s.  The improvements for
random reads are likely to be even bigger.

Signed-off-by: Christoph Hellwig <hch@lst.de>
---
 fs/btrfs/verity.c         |  4 +-
 fs/ext4/readpage.c        |  4 ++
 fs/ext4/verity.c          | 26 +++++------
 fs/f2fs/data.c            |  4 ++
 fs/f2fs/verity.c          | 26 +++++------
 fs/verity/read_metadata.c | 10 ++---
 fs/verity/verify.c        | 92 ++++++++++++++++++++++++++++-----------
 include/linux/fsverity.h  | 34 ++++++++++++---
 8 files changed, 132 insertions(+), 68 deletions(-)

diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c
index 8a4426f8b5fb..cd96fac4739f 100644
--- a/fs/btrfs/verity.c
+++ b/fs/btrfs/verity.c
@@ -697,7 +697,6 @@ int btrfs_get_verity_descriptor(struct inode *inode, void *buf, size_t buf_size)
  *
  * @inode:         inode to read a merkle tree page for
  * @index:         page index relative to the start of the merkle tree
- * @num_ra_pages:  number of pages to readahead. Optional, we ignore it
  *
  * The Merkle tree is stored in the filesystem btree, but its pages are cached
  * with a logical position past EOF in the inode's mapping.
@@ -705,8 +704,7 @@ int btrfs_get_verity_descriptor(struct inode *inode, void *buf, size_t buf_size)
  * Returns the page we read, or an ERR_PTR on error.
  */
 static struct page *btrfs_read_merkle_tree_page(struct inode *inode,
-						pgoff_t index,
-						unsigned long num_ra_pages)
+						pgoff_t index)
 {
 	struct folio *folio;
 	u64 off = (u64)index << PAGE_SHIFT;
diff --git a/fs/ext4/readpage.c b/fs/ext4/readpage.c
index e7f2350c725b..56b3bea657ef 100644
--- a/fs/ext4/readpage.c
+++ b/fs/ext4/readpage.c
@@ -226,6 +226,7 @@ int ext4_mpage_readpages(struct inode *inode,
 	unsigned relative_block = 0;
 	struct ext4_map_blocks map;
 	unsigned int nr_pages, folio_pages;
+	unsigned int did_fsverity_readahead = 0;
 
 	map.m_pblk = 0;
 	map.m_lblk = 0;
@@ -241,6 +242,9 @@ int ext4_mpage_readpages(struct inode *inode,
 		if (rac)
 			folio = readahead_folio(rac);
 
+		if (!did_fsverity_readahead++)
+			fsverity_readahead(inode, folio->index, nr_pages);
+
 		folio_pages = folio_nr_pages(folio);
 		prefetchw(&folio->flags);
 
diff --git a/fs/ext4/verity.c b/fs/ext4/verity.c
index 2cd6fe2fbf64..baa7277dcf1f 100644
--- a/fs/ext4/verity.c
+++ b/fs/ext4/verity.c
@@ -358,28 +358,25 @@ static int ext4_get_verity_descriptor(struct inode *inode, void *buf,
 }
 
 static struct page *ext4_read_merkle_tree_page(struct inode *inode,
-					       pgoff_t index,
-					       unsigned long num_ra_pages)
+					       pgoff_t index)
 {
 	struct folio *folio;
 
 	index += ext4_verity_metadata_pos(inode) >> PAGE_SHIFT;
 
-	folio = __filemap_get_folio(inode->i_mapping, index, FGP_ACCESSED, 0);
-	if (IS_ERR(folio) || !folio_test_uptodate(folio)) {
-		DEFINE_READAHEAD(ractl, NULL, NULL, inode->i_mapping, index);
-
-		if (!IS_ERR(folio))
-			folio_put(folio);
-		else if (num_ra_pages > 1)
-			page_cache_ra_unbounded(&ractl, num_ra_pages, 0);
-		folio = read_mapping_folio(inode->i_mapping, index, NULL);
-		if (IS_ERR(folio))
-			return ERR_CAST(folio);
-	}
+	folio = read_mapping_folio(inode->i_mapping, index, NULL);
+	if (IS_ERR(folio))
+		return ERR_CAST(folio);
 	return folio_file_page(folio, index);
 }
 
+static void ext4_readahead_merkle_tree(struct inode *inode, pgoff_t index,
+		unsigned long nr_pages)
+{
+	index += ext4_verity_metadata_pos(inode) >> PAGE_SHIFT;
+	generic_readahead_merkle_tree(inode, index, nr_pages);
+}
+
 static int ext4_write_merkle_tree_block(struct file *file, const void *buf,
 					u64 pos, unsigned int size)
 {
@@ -393,5 +390,6 @@ const struct fsverity_operations ext4_verityops = {
 	.end_enable_verity	= ext4_end_enable_verity,
 	.get_verity_descriptor	= ext4_get_verity_descriptor,
 	.read_merkle_tree_page	= ext4_read_merkle_tree_page,
+	.readahead_merkle_tree	= ext4_readahead_merkle_tree,
 	.write_merkle_tree_block = ext4_write_merkle_tree_block,
 };
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index c30e69392a62..c01d5b309751 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -2359,6 +2359,7 @@ static int f2fs_mpage_readpages(struct inode *inode,
 	unsigned nr_pages = rac ? readahead_count(rac) : 1;
 	unsigned max_nr_pages = nr_pages;
 	int ret = 0;
+	unsigned int did_fsverity_readahead = 0;
 
 #ifdef CONFIG_F2FS_FS_COMPRESSION
 	if (f2fs_compressed_file(inode)) {
@@ -2383,6 +2384,9 @@ static int f2fs_mpage_readpages(struct inode *inode,
 			prefetchw(&folio->flags);
 		}
 
+		if (!did_fsverity_readahead++)
+			fsverity_readahead(inode, folio->index, nr_pages);
+
 #ifdef CONFIG_F2FS_FS_COMPRESSION
 		index = folio->index;
 
diff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c
index 4326fd72cc72..1ce977937c72 100644
--- a/fs/f2fs/verity.c
+++ b/fs/f2fs/verity.c
@@ -256,28 +256,25 @@ static int f2fs_get_verity_descriptor(struct inode *inode, void *buf,
 }
 
 static struct page *f2fs_read_merkle_tree_page(struct inode *inode,
-					       pgoff_t index,
-					       unsigned long num_ra_pages)
+					       pgoff_t index)
 {
 	struct folio *folio;
 
 	index += f2fs_verity_metadata_pos(inode) >> PAGE_SHIFT;
 
-	folio = f2fs_filemap_get_folio(inode->i_mapping, index, FGP_ACCESSED, 0);
-	if (IS_ERR(folio) || !folio_test_uptodate(folio)) {
-		DEFINE_READAHEAD(ractl, NULL, NULL, inode->i_mapping, index);
-
-		if (!IS_ERR(folio))
-			folio_put(folio);
-		else if (num_ra_pages > 1)
-			page_cache_ra_unbounded(&ractl, num_ra_pages, 0);
-		folio = read_mapping_folio(inode->i_mapping, index, NULL);
-		if (IS_ERR(folio))
-			return ERR_CAST(folio);
-	}
+	folio = read_mapping_folio(inode->i_mapping, index, NULL);
+	if (IS_ERR(folio))
+		return ERR_CAST(folio);
 	return folio_file_page(folio, index);
 }
 
+static void f2fs_readahead_merkle_tree(struct inode *inode, pgoff_t index,
+		unsigned long nr_pages)
+{
+	index += f2fs_verity_metadata_pos(inode) >> PAGE_SHIFT;
+	generic_readahead_merkle_tree(inode, index, nr_pages);
+}
+
 static int f2fs_write_merkle_tree_block(struct file *file, const void *buf,
 					u64 pos, unsigned int size)
 {
@@ -291,5 +288,6 @@ const struct fsverity_operations f2fs_verityops = {
 	.end_enable_verity	= f2fs_end_enable_verity,
 	.get_verity_descriptor	= f2fs_get_verity_descriptor,
 	.read_merkle_tree_page	= f2fs_read_merkle_tree_page,
+	.readahead_merkle_tree	= f2fs_readahead_merkle_tree,
 	.write_merkle_tree_block = f2fs_write_merkle_tree_block,
 };
diff --git a/fs/verity/read_metadata.c b/fs/verity/read_metadata.c
index cba5d6af4e04..3abaa0a4c40e 100644
--- a/fs/verity/read_metadata.c
+++ b/fs/verity/read_metadata.c
@@ -28,24 +28,24 @@ static int fsverity_read_merkle_tree(struct inode *inode,
 	if (offset >= end_offset)
 		return 0;
 	offs_in_page = offset_in_page(offset);
+	index = offset >> PAGE_SHIFT;
 	last_index = (end_offset - 1) >> PAGE_SHIFT;
 
+	__fsverity_readahead(inode, vi, index, last_index - index + 1);
+
 	/*
 	 * Iterate through each Merkle tree page in the requested range and copy
 	 * the requested portion to userspace.  Note that the Merkle tree block
 	 * size isn't important here, as we are returning a byte stream; i.e.,
 	 * we can just work with pages even if the tree block size != PAGE_SIZE.
 	 */
-	for (index = offset >> PAGE_SHIFT; index <= last_index; index++) {
-		unsigned long num_ra_pages =
-			min_t(unsigned long, last_index - index + 1,
-			      inode->i_sb->s_bdi->io_pages);
+	for (; index <= last_index; index++) {
 		unsigned int bytes_to_copy = min_t(u64, end_offset - offset,
 						   PAGE_SIZE - offs_in_page);
 		struct page *page;
 		const void *virt;
 
-		page = vops->read_merkle_tree_page(inode, index, num_ra_pages);
+		page = vops->read_merkle_tree_page(inode, index);
 		if (IS_ERR(page)) {
 			err = PTR_ERR(page);
 			fsverity_err(inode,
diff --git a/fs/verity/verify.c b/fs/verity/verify.c
index 3cc81658e4f3..9a96bad82938 100644
--- a/fs/verity/verify.c
+++ b/fs/verity/verify.c
@@ -9,6 +9,7 @@
 
 #include <linux/bio.h>
 #include <linux/export.h>
+#include <linux/pagemap.h>
 
 #define FS_VERITY_MAX_PENDING_BLOCKS 2
 
@@ -21,7 +22,6 @@ struct fsverity_pending_block {
 struct fsverity_verification_context {
 	struct inode *inode;
 	struct fsverity_info *vi;
-	unsigned long max_ra_pages;
 
 	/*
 	 * This is the queue of data blocks that are pending verification.  When
@@ -37,6 +37,65 @@ struct fsverity_verification_context {
 
 static struct workqueue_struct *fsverity_read_workqueue;
 
+/**
+ * generic_readahead_merkle_tree() - read ahead from merkle tree
+ * @inode:		inode containing fsverity hashes
+ * @index:		first data page offset to read ahead for
+ * @nr_pages:		number of data pages to read ahead for
+ *
+ * Generic fsverity hashes read implementations for file systems storing
+ * the hashes in the page cache of the data inode.  The caller needs to
+ * adjust @index for the fsverity hash offset.
+ */
+void generic_readahead_merkle_tree(struct inode *inode, pgoff_t index,
+		unsigned long nr_pages)
+{
+	struct folio *folio;
+
+	folio = __filemap_get_folio(inode->i_mapping, index, FGP_ACCESSED, 0);
+	if (PTR_ERR(folio) == -ENOENT || !folio_test_uptodate(folio)) {
+		DEFINE_READAHEAD(ractl, NULL, NULL, inode->i_mapping, index);
+
+		page_cache_ra_unbounded(&ractl, nr_pages, 0);
+	}
+	if (!IS_ERR(folio))
+		folio_put(folio);
+}
+EXPORT_SYMBOL_GPL(generic_readahead_merkle_tree);
+
+void __fsverity_readahead(struct inode *inode, const struct fsverity_info *vi,
+		pgoff_t index, unsigned long nr_pages)
+{
+	const struct merkle_tree_params *params = &vi->tree_params;
+	loff_t data_start_pos = (loff_t)index << PAGE_SHIFT;
+	u64 start_hidx = data_start_pos >> params->log_blocksize;
+	u64 end_hidx = (data_start_pos + ((nr_pages - 1) << PAGE_SHIFT)) >>
+			params->log_blocksize;
+	int level;
+
+	if (!inode->i_sb->s_vop->readahead_merkle_tree)
+		return;
+	if (unlikely(data_start_pos >= inode->i_size))
+		return;
+
+	for (level = 0; level < params->num_levels; level++) {
+		unsigned long level_start = params->level_start[level];
+		unsigned long next_start_hidx = start_hidx >> params->log_arity;
+		unsigned long next_end_hidx = end_hidx >> params->log_arity;
+		unsigned long start_idx = (level_start + next_start_hidx) >>
+				params->log_blocks_per_page;
+		unsigned long end_idx = (level_start + next_end_hidx) >>
+				params->log_blocks_per_page;
+
+		inode->i_sb->s_vop->readahead_merkle_tree(inode, start_idx,
+				end_idx - start_idx + 1);
+
+		start_hidx = next_start_hidx;
+		end_hidx = next_end_hidx;
+	}
+}
+EXPORT_SYMBOL_GPL(__fsverity_readahead);
+
 /*
  * Returns true if the hash block with index @hblock_idx in the tree, located in
  * @hpage, has already been verified.
@@ -114,8 +173,7 @@ static bool is_hash_block_verified(struct fsverity_info *vi, struct page *hpage,
  * Return: %true if the data block is valid, else %false.
  */
 static bool verify_data_block(struct inode *inode, struct fsverity_info *vi,
-			      const struct fsverity_pending_block *dblock,
-			      unsigned long max_ra_pages)
+			      const struct fsverity_pending_block *dblock)
 {
 	const u64 data_pos = dblock->pos;
 	const struct merkle_tree_params *params = &vi->tree_params;
@@ -200,8 +258,7 @@ static bool verify_data_block(struct inode *inode, struct fsverity_info *vi,
 			  (params->block_size - 1);
 
 		hpage = inode->i_sb->s_vop->read_merkle_tree_page(inode,
-				hpage_idx, level == 0 ? min(max_ra_pages,
-					params->tree_pages - hpage_idx) : 0);
+				hpage_idx);
 		if (IS_ERR(hpage)) {
 			fsverity_err(inode,
 				     "Error %ld reading Merkle tree page %lu",
@@ -272,12 +329,10 @@ static bool verify_data_block(struct inode *inode, struct fsverity_info *vi,
 
 static void
 fsverity_init_verification_context(struct fsverity_verification_context *ctx,
-				   struct inode *inode,
-				   unsigned long max_ra_pages)
+				   struct inode *inode)
 {
 	ctx->inode = inode;
 	ctx->vi = fsverity_get_info(inode);
-	ctx->max_ra_pages = max_ra_pages;
 	ctx->num_pending = 0;
 	if (ctx->vi->tree_params.hash_alg->algo_id == HASH_ALGO_SHA256 &&
 	    sha256_finup_2x_is_optimized())
@@ -320,8 +375,7 @@ fsverity_verify_pending_blocks(struct fsverity_verification_context *ctx)
 	}
 
 	for (i = 0; i < ctx->num_pending; i++) {
-		if (!verify_data_block(ctx->inode, vi, &ctx->pending_blocks[i],
-				       ctx->max_ra_pages))
+		if (!verify_data_block(ctx->inode, vi, &ctx->pending_blocks[i]))
 			return false;
 	}
 	fsverity_clear_pending_blocks(ctx);
@@ -371,7 +425,7 @@ bool fsverity_verify_blocks(struct folio *folio, size_t len, size_t offset)
 {
 	struct fsverity_verification_context ctx;
 
-	fsverity_init_verification_context(&ctx, folio->mapping->host, 0);
+	fsverity_init_verification_context(&ctx, folio->mapping->host);
 
 	if (fsverity_add_data_blocks(&ctx, folio, len, offset) &&
 	    fsverity_verify_pending_blocks(&ctx))
@@ -401,22 +455,8 @@ void fsverity_verify_bio(struct bio *bio)
 	struct inode *inode = bio_first_folio_all(bio)->mapping->host;
 	struct fsverity_verification_context ctx;
 	struct folio_iter fi;
-	unsigned long max_ra_pages = 0;
-
-	if (bio->bi_opf & REQ_RAHEAD) {
-		/*
-		 * If this bio is for data readahead, then we also do readahead
-		 * of the first (largest) level of the Merkle tree.  Namely,
-		 * when a Merkle tree page is read, we also try to piggy-back on
-		 * some additional pages -- up to 1/4 the number of data pages.
-		 *
-		 * This improves sequential read performance, as it greatly
-		 * reduces the number of I/O requests made to the Merkle tree.
-		 */
-		max_ra_pages = bio->bi_iter.bi_size >> (PAGE_SHIFT + 2);
-	}
 
-	fsverity_init_verification_context(&ctx, inode, max_ra_pages);
+	fsverity_init_verification_context(&ctx, inode);
 
 	bio_for_each_folio_all(fi, bio) {
 		if (!fsverity_add_data_blocks(&ctx, fi.folio, fi.length,
diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h
index deb6b2303d64..84aaa09e07e2 100644
--- a/include/linux/fsverity.h
+++ b/include/linux/fsverity.h
@@ -90,10 +90,6 @@ struct fsverity_operations {
 	 *
 	 * @inode: the inode
 	 * @index: 0-based index of the page within the Merkle tree
-	 * @num_ra_pages: The number of Merkle tree pages that should be
-	 *		  prefetched starting at @index if the page at @index
-	 *		  isn't already cached.  Implementations may ignore this
-	 *		  argument; it's only a performance optimization.
 	 *
 	 * This can be called at any time on an open verity file.  It may be
 	 * called by multiple processes concurrently, even with the same page.
@@ -103,8 +99,10 @@ struct fsverity_operations {
 	 * Return: the page on success, ERR_PTR() on failure
 	 */
 	struct page *(*read_merkle_tree_page)(struct inode *inode,
-					      pgoff_t index,
-					      unsigned long num_ra_pages);
+					      pgoff_t index);
+
+	void (*readahead_merkle_tree)(struct inode *inode, pgoff_t index,
+			unsigned long nr_pages);
 
 	/**
 	 * Write a Merkle tree block to the given inode.
@@ -246,4 +244,28 @@ static inline bool fsverity_active(const struct inode *inode)
 	return fsverity_get_info(inode) != NULL;
 }
 
+/**
+ * fsverity_readahead() - kick off readahead on fsverity hashes
+ * @inode:		inode containing fsverity hashes
+ * @index:		first data page offset to read ahead for
+ * @nr_pages:		number of data pages to read ahead for
+ *
+ * Start readahead on fsverity hashes.  To be called from the file systems
+ * ->read_folio and ->readahead methods to ensure that the hashes are
+ * already cached on completion of the file data read if possible.
+ */
+void __fsverity_readahead(struct inode *inode, const struct fsverity_info *vi,
+		pgoff_t index, unsigned long nr_pages);
+static inline void fsverity_readahead(struct inode *inode,
+		pgoff_t index, unsigned long nr_pages)
+{
+	const struct fsverity_info *vi = fsverity_get_info(inode);
+
+	if (vi)
+		__fsverity_readahead(inode, vi, index, nr_pages);
+}
+
+void generic_readahead_merkle_tree(struct inode *inode, pgoff_t index,
+		unsigned long nr_pages);
+
 #endif	/* _LINUX_FSVERITY_H */
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/6] fs,fsverity: handle fsverity in generic_file_open
  2026-01-19  6:22 ` [PATCH 3/6] fs,fsverity: handle fsverity in generic_file_open Christoph Hellwig
@ 2026-01-19  9:05   ` Jan Kara
  2026-01-19  9:26     ` Christoph Hellwig
  2026-01-19 10:02   ` Christian Brauner
  1 sibling, 1 reply; 16+ messages in thread
From: Jan Kara @ 2026-01-19  9:05 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Eric Biggers, Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	linux-fsdevel, linux-btrfs, linux-ext4, linux-f2fs-devel,
	fsverity

On Mon 19-01-26 07:22:44, Christoph Hellwig wrote:
> Call into fsverity_file_open from generic_file_open instead of requiring
> the file system to handle it explicitly.
> 
> Signed-off-by: Christoph Hellwig <hch@lst.de>
...
> -int generic_file_open(struct inode * inode, struct file * filp)
> +int generic_file_open(struct inode *inode, struct file *filp)
>  {
>  	if (!(filp->f_flags & O_LARGEFILE) && i_size_read(inode) > MAX_NON_LFS)
>  		return -EOVERFLOW;
> +	if (IS_ENABLED(CONFIG_FS_VERITY) && IS_VERITY(inode)) {
> +		if (filp->f_mode & FMODE_WRITE)
> +			return -EPERM;
> +		return fsverity_file_open(inode, filp);
> +	}

Why do you check f_mode here when fsverity_file_open() checks for it as
well?

> -int __fsverity_file_open(struct inode *inode, struct file *filp)
> +/*
> + * When opening a verity file, deny the open if it is for writing.  Otherwise,
> + * set up the inode's verity info if not already done.
> + *
> + * When combined with fscrypt, this must be called after fscrypt_file_open().
> + * Otherwise, we won't have the key set up to decrypt the verity metadata.
> + */
> +int fsverity_file_open(struct inode *inode, struct file *filp)
>  {
>  	if (filp->f_mode & FMODE_WRITE)
>  		return -EPERM;
>  	return ensure_verity_info(inode);
>  }
> -EXPORT_SYMBOL_GPL(__fsverity_file_open);

								Honza
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 4/6] fsverity: use a hashtable to find the fsverity_info
  2026-01-19  6:22 ` [PATCH 4/6] fsverity: use a hashtable to find the fsverity_info Christoph Hellwig
@ 2026-01-19  9:21   ` Jan Kara
  2026-01-19  9:27     ` Christoph Hellwig
  2026-01-19 19:05   ` Eric Biggers
  1 sibling, 1 reply; 16+ messages in thread
From: Jan Kara @ 2026-01-19  9:21 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Eric Biggers, Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	linux-fsdevel, linux-btrfs, linux-ext4, linux-f2fs-devel,
	fsverity

On Mon 19-01-26 07:22:45, Christoph Hellwig wrote:
> Use the kernel's resizable hash table to find the fsverity_info.  This
> way file systems that want to support fsverity don't have to bloat
> every inode in the system with an extra pointer.  The tradeoff is that
> looking up the fsverity_info is a bit more expensive now, but the main
> operations are still dominated by I/O and hashing overhead.
> 
> Signed-off-by: Christoph Hellwig <hch@lst.de>

...

> diff --git a/fs/verity/enable.c b/fs/verity/enable.c
> index 95ec42b84797..91cada0d455c 100644
> --- a/fs/verity/enable.c
> +++ b/fs/verity/enable.c
> @@ -264,9 +264,24 @@ static int enable_verity(struct file *filp,
>  		goto rollback;
>  	}
>  
> +	/*
> +	 * Add the fsverity_info into the hash table before finishing the
> +	 * initialization.  This ensures we don't have to undo the enabling when
> +	 * memory allocation for the hash table fails.  This is safe because
> +	 * looking up the fsverity_info always first checks the S_VERITY flag on
> +	 * the inode, which will only be set at the very end of the
> +	 * ->end_enable_verity method.
> +	 */
> +	err = fsverity_set_info(vi);
> +	if (err)
> +		goto rollback;

OK, but since __fsverity_get_info() is just rhashtable_lookup_fast() what
prevents the CPU from reordering the hash table reads before the S_VERITY
check? I think you need a barrier in fsverity_get_info() to enforce the
proper ordering. The matching ordering during setting of S_VERITY is
implied by cmpxchg used to manipulate i_flags so that part should be fine.

								Honza
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/6] fs,fsverity: handle fsverity in generic_file_open
  2026-01-19  9:05   ` Jan Kara
@ 2026-01-19  9:26     ` Christoph Hellwig
  0 siblings, 0 replies; 16+ messages in thread
From: Christoph Hellwig @ 2026-01-19  9:26 UTC (permalink / raw)
  To: Jan Kara
  Cc: Christoph Hellwig, Eric Biggers, Al Viro, Christian Brauner,
	David Sterba, Theodore Ts'o, Jaegeuk Kim, Chao Yu,
	Andrey Albershteyn, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity

On Mon, Jan 19, 2026 at 10:05:56AM +0100, Jan Kara wrote:
> > +	if (IS_ENABLED(CONFIG_FS_VERITY) && IS_VERITY(inode)) {
> > +		if (filp->f_mode & FMODE_WRITE)
> > +			return -EPERM;
> > +		return fsverity_file_open(inode, filp);
> > +	}
> 
> Why do you check f_mode here when fsverity_file_open() checks for it as
> well?

Probably because it's a left over from when I tried to open code
fsverity_file_open here.  I'll fix it up.


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 4/6] fsverity: use a hashtable to find the fsverity_info
  2026-01-19  9:21   ` Jan Kara
@ 2026-01-19  9:27     ` Christoph Hellwig
  0 siblings, 0 replies; 16+ messages in thread
From: Christoph Hellwig @ 2026-01-19  9:27 UTC (permalink / raw)
  To: Jan Kara
  Cc: Christoph Hellwig, Eric Biggers, Al Viro, Christian Brauner,
	David Sterba, Theodore Ts'o, Jaegeuk Kim, Chao Yu,
	Andrey Albershteyn, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity

On Mon, Jan 19, 2026 at 10:21:00AM +0100, Jan Kara wrote:
> OK, but since __fsverity_get_info() is just rhashtable_lookup_fast() what
> prevents the CPU from reordering the hash table reads before the S_VERITY
> check? I think you need a barrier in fsverity_get_info() to enforce the
> proper ordering. The matching ordering during setting of S_VERITY is
> implied by cmpxchg used to manipulate i_flags so that part should be fine.

Yes, probably.


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: fsverity optimzations and speedups
  2026-01-19  6:22 fsverity optimzations and speedups Christoph Hellwig
                   ` (5 preceding siblings ...)
  2026-01-19  6:22 ` [PATCH 6/6] fsverity: kick off hash readahead at data I/O submission time Christoph Hellwig
@ 2026-01-19  9:37 ` Christian Brauner
  6 siblings, 0 replies; 16+ messages in thread
From: Christian Brauner @ 2026-01-19  9:37 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Eric Biggers, Al Viro, Jan Kara, David Sterba, Theodore Ts'o,
	Jaegeuk Kim, Chao Yu, Andrey Albershteyn, linux-fsdevel,
	linux-btrfs, linux-ext4, linux-f2fs-devel, fsverity

On Mon, Jan 19, 2026 at 07:22:41AM +0100, Christoph Hellwig wrote:
> Hi all,
> 
> this series has a hodge podge of fsverity enhances that I looked into as
> part of the review of the xfs fsverity support series.
> 
> The first three patches call fsverity code from VFS code instead of
> requiring a lot of boilerplate in the file systems.  The first fixes a

While I'm not super excited about that I can see that it's beneficial.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/6] fs,fsverity: handle fsverity in generic_file_open
  2026-01-19  6:22 ` [PATCH 3/6] fs,fsverity: handle fsverity in generic_file_open Christoph Hellwig
  2026-01-19  9:05   ` Jan Kara
@ 2026-01-19 10:02   ` Christian Brauner
  2026-01-19 12:06     ` Christoph Hellwig
  1 sibling, 1 reply; 16+ messages in thread
From: Christian Brauner @ 2026-01-19 10:02 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Eric Biggers, Al Viro, Jan Kara, David Sterba, Theodore Ts'o,
	Jaegeuk Kim, Chao Yu, Andrey Albershteyn, linux-fsdevel,
	linux-btrfs, linux-ext4, linux-f2fs-devel, fsverity

On Mon, Jan 19, 2026 at 07:22:44AM +0100, Christoph Hellwig wrote:
> Call into fsverity_file_open from generic_file_open instead of requiring
> the file system to handle it explicitly.
> 
> Signed-off-by: Christoph Hellwig <hch@lst.de>
> ---
>  fs/btrfs/file.c          |  6 ------
>  fs/ext4/file.c           |  4 ----
>  fs/f2fs/file.c           |  4 ----
>  fs/open.c                |  8 +++++++-
>  fs/verity/open.c         | 10 ++++++++--
>  include/linux/fsverity.h | 32 +-------------------------------
>  6 files changed, 16 insertions(+), 48 deletions(-)
> 
> diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
> index 1abc7ed2990e..4b3a31b2b52e 100644
> --- a/fs/btrfs/file.c
> +++ b/fs/btrfs/file.c
> @@ -3808,16 +3808,10 @@ static loff_t btrfs_file_llseek(struct file *file, loff_t offset, int whence)
>  
>  static int btrfs_file_open(struct inode *inode, struct file *filp)
>  {
> -	int ret;
> -
>  	if (unlikely(btrfs_is_shutdown(inode_to_fs_info(inode))))
>  		return -EIO;
>  
>  	filp->f_mode |= FMODE_NOWAIT | FMODE_CAN_ODIRECT;
> -
> -	ret = fsverity_file_open(inode, filp);
> -	if (ret)
> -		return ret;
>  	return generic_file_open(inode, filp);
>  }
>  
> diff --git a/fs/ext4/file.c b/fs/ext4/file.c
> index 7a8b30932189..a7dc8c10273e 100644
> --- a/fs/ext4/file.c
> +++ b/fs/ext4/file.c
> @@ -906,10 +906,6 @@ static int ext4_file_open(struct inode *inode, struct file *filp)
>  	if (ret)
>  		return ret;
>  
> -	ret = fsverity_file_open(inode, filp);
> -	if (ret)
> -		return ret;
> -
>  	/*
>  	 * Set up the jbd2_inode if we are opening the inode for
>  	 * writing and the journal is present
> diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
> index da029fed4e5a..f1510ab657b6 100644
> --- a/fs/f2fs/file.c
> +++ b/fs/f2fs/file.c
> @@ -624,10 +624,6 @@ static int f2fs_file_open(struct inode *inode, struct file *filp)
>  	if (!f2fs_is_compress_backend_ready(inode))
>  		return -EOPNOTSUPP;
>  
> -	err = fsverity_file_open(inode, filp);
> -	if (err)
> -		return err;
> -
>  	filp->f_mode |= FMODE_NOWAIT;
>  	filp->f_mode |= FMODE_CAN_ODIRECT;
>  
> diff --git a/fs/open.c b/fs/open.c
> index f328622061c5..dea93bab8795 100644
> --- a/fs/open.c
> +++ b/fs/open.c
> @@ -10,6 +10,7 @@
>  #include <linux/file.h>
>  #include <linux/fdtable.h>
>  #include <linux/fsnotify.h>
> +#include <linux/fsverity.h>
>  #include <linux/module.h>
>  #include <linux/tty.h>
>  #include <linux/namei.h>
> @@ -1604,10 +1605,15 @@ SYSCALL_DEFINE0(vhangup)
>   * the caller didn't specify O_LARGEFILE.  On 64bit systems we force
>   * on this flag in sys_open.
>   */
> -int generic_file_open(struct inode * inode, struct file * filp)
> +int generic_file_open(struct inode *inode, struct file *filp)
>  {
>  	if (!(filp->f_flags & O_LARGEFILE) && i_size_read(inode) > MAX_NON_LFS)
>  		return -EOVERFLOW;
> +	if (IS_ENABLED(CONFIG_FS_VERITY) && IS_VERITY(inode)) {
> +		if (filp->f_mode & FMODE_WRITE)
> +			return -EPERM;
> +		return fsverity_file_open(inode, filp);
> +	}

This is the only one where I'm not happy about the location.
This hides the ordering requirement between fsverity and fscrypt. It's
easier to miss now. This also really saves very little compared to the
other changes. So I wonder whether it's really that big of a deal to
have the call located in the open routines of the filesystems.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 3/6] fs,fsverity: handle fsverity in generic_file_open
  2026-01-19 10:02   ` Christian Brauner
@ 2026-01-19 12:06     ` Christoph Hellwig
  0 siblings, 0 replies; 16+ messages in thread
From: Christoph Hellwig @ 2026-01-19 12:06 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Christoph Hellwig, Eric Biggers, Al Viro, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	linux-fsdevel, linux-btrfs, linux-ext4, linux-f2fs-devel,
	fsverity

On Mon, Jan 19, 2026 at 11:02:37AM +0100, Christian Brauner wrote:
> > +	if (IS_ENABLED(CONFIG_FS_VERITY) && IS_VERITY(inode)) {
> > +		if (filp->f_mode & FMODE_WRITE)
> > +			return -EPERM;
> > +		return fsverity_file_open(inode, filp);
> > +	}
> 
> This is the only one where I'm not happy about the location.
> This hides the ordering requirement between fsverity and fscrypt. It's
> easier to miss now. This also really saves very little compared to the
> other changes. So I wonder whether it's really that big of a deal to
> have the call located in the open routines of the filesystems.

So my idea was to do a similar pass for fscrypt eventually, and enforce
the ordering in one place, instead of relying on file systems to get it
right.  I'd be fine with delaying this patch until then and give it
another try.  The good thing is that unlike say the stat hook fsverity
will simply not work without wiring this up, so it can't be easily
forgotten.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 4/6] fsverity: use a hashtable to find the fsverity_info
  2026-01-19  6:22 ` [PATCH 4/6] fsverity: use a hashtable to find the fsverity_info Christoph Hellwig
  2026-01-19  9:21   ` Jan Kara
@ 2026-01-19 19:05   ` Eric Biggers
  2026-01-20  7:35     ` Christoph Hellwig
  1 sibling, 1 reply; 16+ messages in thread
From: Eric Biggers @ 2026-01-19 19:05 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	linux-fsdevel, linux-btrfs, linux-ext4, linux-f2fs-devel,
	fsverity

On Mon, Jan 19, 2026 at 07:22:45AM +0100, Christoph Hellwig wrote:
> Use the kernel's resizable hash table to find the fsverity_info.  This
> way file systems that want to support fsverity don't have to bloat
> every inode in the system with an extra pointer.  The tradeoff is that
> looking up the fsverity_info is a bit more expensive now, but the main
> operations are still dominated by I/O and hashing overhead.
> 
> Signed-off-by: Christoph Hellwig <hch@lst.de>

Has anything changed from my last feedback at
https://lore.kernel.org/linux-fsdevel/20250810170311.GA16624@sol/ ?

Any additional data on the cycles and icache footprint added to data
verification?  The preliminary results didn't look all that good to me.

It also seems odd to put this in an "fsverity optimzations and speedups"
patchset, given that it's the opposite.

- Eric

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 4/6] fsverity: use a hashtable to find the fsverity_info
  2026-01-19 19:05   ` Eric Biggers
@ 2026-01-20  7:35     ` Christoph Hellwig
  0 siblings, 0 replies; 16+ messages in thread
From: Christoph Hellwig @ 2026-01-20  7:35 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Christoph Hellwig, Al Viro, Christian Brauner, Jan Kara,
	David Sterba, Theodore Ts'o, Jaegeuk Kim, Chao Yu,
	Andrey Albershteyn, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity, willy

On Mon, Jan 19, 2026 at 11:05:36AM -0800, Eric Biggers wrote:
> On Mon, Jan 19, 2026 at 07:22:45AM +0100, Christoph Hellwig wrote:
> > Use the kernel's resizable hash table to find the fsverity_info.  This
> > way file systems that want to support fsverity don't have to bloat
> > every inode in the system with an extra pointer.  The tradeoff is that
> > looking up the fsverity_info is a bit more expensive now, but the main
> > operations are still dominated by I/O and hashing overhead.
> > 
> > Signed-off-by: Christoph Hellwig <hch@lst.de>
> 
> Has anything changed from my last feedback at
> https://lore.kernel.org/linux-fsdevel/20250810170311.GA16624@sol/ ?
> 
> Any additional data on the cycles and icache footprint added to data
> verification?  The preliminary results didn't look all that good to me.

Nothing has changed, as as expected by then and now the lookup overhead
is completely dwarfed by other parts of the read, even when the
data and hashes are already cached.  Of course if you look into the lookup
in itself, any kind of data structure will be significantly more expensive
than a pointer dereference and nothing will change that.

> It also seems odd to put this in an "fsverity optimzations and speedups"
> patchset, given that it's the opposite.

It optimizes the memory usage and doesn't require bloating the inode
for new file systems adding fsverity support.  size of the inode is
a major concern, and is even more so for a feature that is not
commonly used and then not for most files.


^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2026-01-20  7:35 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-19  6:22 fsverity optimzations and speedups Christoph Hellwig
2026-01-19  6:22 ` [PATCH 1/6] fs,fsverity: reject size changes on fsverity files in setattr_prepare Christoph Hellwig
2026-01-19  6:22 ` [PATCH 2/6] fs,fsverity: clear out fsverity_info from common code Christoph Hellwig
2026-01-19  6:22 ` [PATCH 3/6] fs,fsverity: handle fsverity in generic_file_open Christoph Hellwig
2026-01-19  9:05   ` Jan Kara
2026-01-19  9:26     ` Christoph Hellwig
2026-01-19 10:02   ` Christian Brauner
2026-01-19 12:06     ` Christoph Hellwig
2026-01-19  6:22 ` [PATCH 4/6] fsverity: use a hashtable to find the fsverity_info Christoph Hellwig
2026-01-19  9:21   ` Jan Kara
2026-01-19  9:27     ` Christoph Hellwig
2026-01-19 19:05   ` Eric Biggers
2026-01-20  7:35     ` Christoph Hellwig
2026-01-19  6:22 ` [PATCH 5/6] fsverity: pass struct file to ->write_merkle_tree_block Christoph Hellwig
2026-01-19  6:22 ` [PATCH 6/6] fsverity: kick off hash readahead at data I/O submission time Christoph Hellwig
2026-01-19  9:37 ` fsverity optimzations and speedups Christian Brauner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox