public inbox for linux-btrfs@vger.kernel.org
 help / color / mirror / Atom feed
* fsverity speedup and memory usage optimization v5
@ 2026-02-02  6:06 Christoph Hellwig
  2026-02-02  6:06 ` [PATCH 01/11] fsverity: don't issue readahead for non-ENOENT errors from __filemap_get_folio Christoph Hellwig
                   ` (11 more replies)
  0 siblings, 12 replies; 28+ messages in thread
From: Christoph Hellwig @ 2026-02-02  6:06 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	Matthew Wilcox, 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 part optimizes the fsverity 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.

The second part avoids the need for a pointer in every inode for fsverity
and instead uses a rhashtable lookup, which is done once per read_folio
or ->readahead invocation plus for btrfs only for each bio completion.
Right now this does not increse the number of inodes in
each slab, but for ext4 we are getting very close to that (within
16 bytes by my count).

Changes since v5:
 - drop already merged patches
 - fix a bisection hazard for non-ENOENT error returns from
   generic_read_merkle_tree_page
 - don't recurse on invalidate_lock
 - refactor page_cache_ra_unbounded locking to support the above
 - refactor ext4 and f2fs fsverity readahead to remove the need for the
   first_folio branch in the main readpages loop

Changes since v4:
 - drop the constification of ctx->vi again
 - fix __filemap_get_folio error handling again
 - don't use "pgoff_t long"
 - improve documentation of the new pagecache helpers
 - reduce the number of fsverity_info lookups in btrfs
 - improve the documentation for fsverity_active

Changes since v2:
 - use sizeof_field for .key_len
 - fix a rebase error that caused an extra fsverity_get_info in
   fsverity_init_verification_context
 - add verify.o to the build in the correct patch
 - fix handling of non-ENOENT ERR_PTR folios in
   generic_readahead_merkle_tree
 - split fixing the __filemap_get_folio error handling into a
   separate patch
 - fix the readahead range in fsverity_read_merkle_tree
 - remove __fsverity_readahead as a result of the above
 - simplify the start/end_hidx calculation in fsverity_readahead
 - drop the > i_size check in fsverity_readahead
 - use pgoff_t where applicable
 - constify fsverity_info pointers in the verification path
 - use IS_ENABLED to disable code not used for non-fsverity builds in
   ext4 and f2fs
 - allow bisection for non-fsverity builds by provinding a stub
   fsverity_info_addr prototype
 - drop the now superflous inode argument to
   fsverity_init_verification_context
 - improve the kerneldoc for fsverity_readahead
 - improve various commit messages
 - fix the barrier placement in fsverity_active
 - mark fsverity_active to work around stupid compilers

Changes since v1:
 - reorder to keep the most controversial part last
 - drop moving the open handling to common code (for now)
 - factor the page cache read code into common code
 - reduce the number of hash lookups
 - add a barrier in the fsverity_active that pairs with the cmpxchg
   that sets the inode flag.

Diffstat:
 fs/btrfs/btrfs_inode.h       |    4 -
 fs/btrfs/extent_io.c         |   53 ++++++++++------
 fs/btrfs/inode.c             |    3 
 fs/btrfs/verity.c            |    6 -
 fs/buffer.c                  |   25 +++----
 fs/ext4/ext4.h               |    8 --
 fs/ext4/inode.c              |   27 --------
 fs/ext4/readpage.c           |   62 +++++++++++++++----
 fs/ext4/super.c              |    3 
 fs/ext4/verity.c             |   15 +++-
 fs/f2fs/compress.c           |    7 +-
 fs/f2fs/data.c               |   88 +++++++++++++++++----------
 fs/f2fs/f2fs.h               |   12 ---
 fs/f2fs/file.c               |    2 
 fs/f2fs/super.c              |    3 
 fs/f2fs/verity.c             |   15 +++-
 fs/verity/enable.c           |   30 +++++----
 fs/verity/fsverity_private.h |   21 +++---
 fs/verity/open.c             |   77 +++++++++++++++---------
 fs/verity/pagecache.c        |   44 ++++++++++---
 fs/verity/read_metadata.c    |   19 ++++--
 fs/verity/verify.c           |   94 ++++++++++++++++++-----------
 include/linux/fsverity.h     |  136 ++++++++++++++++++++++---------------------
 mm/readahead.c               |   13 ++--
 24 files changed, 440 insertions(+), 327 deletions(-)

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

* [PATCH 01/11] fsverity: don't issue readahead for non-ENOENT errors from __filemap_get_folio
  2026-02-02  6:06 fsverity speedup and memory usage optimization v5 Christoph Hellwig
@ 2026-02-02  6:06 ` Christoph Hellwig
  2026-02-02 13:23   ` Jan Kara
  2026-02-17 21:14   ` [f2fs-dev] " patchwork-bot+f2fs
  2026-02-02  6:06 ` [PATCH 02/11] readahead: push invalidate_lock out of page_cache_ra_unbounded Christoph Hellwig
                   ` (10 subsequent siblings)
  11 siblings, 2 replies; 28+ messages in thread
From: Christoph Hellwig @ 2026-02-02  6:06 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	Matthew Wilcox, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity

Issuing more reads on errors is not a good idea, especially when the
most common error here is -ENOMEM.

Signed-off-by: Christoph Hellwig <hch@lst.de>
---
 fs/verity/pagecache.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/fs/verity/pagecache.c b/fs/verity/pagecache.c
index 01c652bc802f..1a88decace53 100644
--- a/fs/verity/pagecache.c
+++ b/fs/verity/pagecache.c
@@ -22,7 +22,8 @@ struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index,
 	struct folio *folio;
 
 	folio = __filemap_get_folio(inode->i_mapping, index, FGP_ACCESSED, 0);
-	if (IS_ERR(folio) || !folio_test_uptodate(folio)) {
+	if (folio == ERR_PTR(-ENOENT) ||
+	    (!IS_ERR(folio) && !folio_test_uptodate(folio))) {
 		DEFINE_READAHEAD(ractl, NULL, NULL, inode->i_mapping, index);
 
 		if (!IS_ERR(folio))
@@ -30,9 +31,9 @@ struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index,
 		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);
 	}
+	if (IS_ERR(folio))
+		return ERR_CAST(folio);
 	return folio_file_page(folio, index);
 }
 EXPORT_SYMBOL_GPL(generic_read_merkle_tree_page);
-- 
2.47.3


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

* [PATCH 02/11] readahead: push invalidate_lock out of page_cache_ra_unbounded
  2026-02-02  6:06 fsverity speedup and memory usage optimization v5 Christoph Hellwig
  2026-02-02  6:06 ` [PATCH 01/11] fsverity: don't issue readahead for non-ENOENT errors from __filemap_get_folio Christoph Hellwig
@ 2026-02-02  6:06 ` Christoph Hellwig
  2026-02-02 13:36   ` Jan Kara
  2026-02-02 15:11   ` Matthew Wilcox
  2026-02-02  6:06 ` [PATCH 03/11] ext4: move ->read_folio and ->readahead to readahead.c Christoph Hellwig
                   ` (9 subsequent siblings)
  11 siblings, 2 replies; 28+ messages in thread
From: Christoph Hellwig @ 2026-02-02  6:06 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	Matthew Wilcox, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity

Require the invalidate_lock to be held over calls to
page_cache_ra_unbounded instead of acquiring it in this function.

This prepares for calling page_cache_ra_unbounded from ->readahead for
fsverity read-ahead.

Signed-off-by: Christoph Hellwig <hch@lst.de>
---
 fs/f2fs/file.c        |  2 ++
 fs/verity/pagecache.c |  7 +++++--
 mm/readahead.c        | 13 ++++++++-----
 3 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
index da029fed4e5a..c9b9fcdd0cae 100644
--- a/fs/f2fs/file.c
+++ b/fs/f2fs/file.c
@@ -4418,7 +4418,9 @@ static int redirty_blocks(struct inode *inode, pgoff_t page_idx, int len)
 	pgoff_t redirty_idx = page_idx;
 	int page_len = 0, ret = 0;
 
+	filemap_invalidate_lock_shared(mapping);
 	page_cache_ra_unbounded(&ractl, len, 0);
+	filemap_invalidate_unlock_shared(mapping);
 
 	do {
 		folio = read_cache_folio(mapping, page_idx, NULL, NULL);
diff --git a/fs/verity/pagecache.c b/fs/verity/pagecache.c
index 1a88decace53..8e0d6fde802f 100644
--- a/fs/verity/pagecache.c
+++ b/fs/verity/pagecache.c
@@ -26,10 +26,13 @@ struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index,
 	    (!IS_ERR(folio) && !folio_test_uptodate(folio))) {
 		DEFINE_READAHEAD(ractl, NULL, NULL, inode->i_mapping, index);
 
-		if (!IS_ERR(folio))
+		if (!IS_ERR(folio)) {
 			folio_put(folio);
-		else if (num_ra_pages > 1)
+		} else if (num_ra_pages > 1) {
+			filemap_invalidate_lock_shared(inode->i_mapping);
 			page_cache_ra_unbounded(&ractl, num_ra_pages, 0);
+			filemap_invalidate_unlock_shared(inode->i_mapping);
+		}
 		folio = read_mapping_folio(inode->i_mapping, index, NULL);
 	}
 	if (IS_ERR(folio))
diff --git a/mm/readahead.c b/mm/readahead.c
index b415c9969176..25f81124beb6 100644
--- a/mm/readahead.c
+++ b/mm/readahead.c
@@ -204,7 +204,8 @@ static struct folio *ractl_alloc_folio(struct readahead_control *ractl,
  * not the function you want to call.  Use page_cache_async_readahead()
  * or page_cache_sync_readahead() instead.
  *
- * Context: File is referenced by caller.  Mutexes may be held by caller.
+ * Context: File is referenced by caller, and ractl->mapping->invalidate_lock
+ * must be held by the caller in shared mode.  Mutexes may be held by caller.
  * May sleep, but will not reenter filesystem to reclaim memory.
  */
 void page_cache_ra_unbounded(struct readahead_control *ractl,
@@ -228,9 +229,10 @@ void page_cache_ra_unbounded(struct readahead_control *ractl,
 	 */
 	unsigned int nofs = memalloc_nofs_save();
 
+	lockdep_assert_held_read(&mapping->invalidate_lock);
+
 	trace_page_cache_ra_unbounded(mapping->host, index, nr_to_read,
 				      lookahead_size);
-	filemap_invalidate_lock_shared(mapping);
 	index = mapping_align_index(mapping, index);
 
 	/*
@@ -300,7 +302,6 @@ void page_cache_ra_unbounded(struct readahead_control *ractl,
 	 * will then handle the error.
 	 */
 	read_pages(ractl);
-	filemap_invalidate_unlock_shared(mapping);
 	memalloc_nofs_restore(nofs);
 }
 EXPORT_SYMBOL_GPL(page_cache_ra_unbounded);
@@ -314,9 +315,9 @@ EXPORT_SYMBOL_GPL(page_cache_ra_unbounded);
 static void do_page_cache_ra(struct readahead_control *ractl,
 		unsigned long nr_to_read, unsigned long lookahead_size)
 {
-	struct inode *inode = ractl->mapping->host;
+	struct address_space *mapping = ractl->mapping;
 	unsigned long index = readahead_index(ractl);
-	loff_t isize = i_size_read(inode);
+	loff_t isize = i_size_read(mapping->host);
 	pgoff_t end_index;	/* The last page we want to read */
 
 	if (isize == 0)
@@ -329,7 +330,9 @@ static void do_page_cache_ra(struct readahead_control *ractl,
 	if (nr_to_read > end_index - index)
 		nr_to_read = end_index - index + 1;
 
+	filemap_invalidate_lock_shared(mapping);
 	page_cache_ra_unbounded(ractl, nr_to_read, lookahead_size);
+	filemap_invalidate_unlock_shared(mapping);
 }
 
 /*
-- 
2.47.3


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

* [PATCH 03/11] ext4: move ->read_folio and ->readahead to readahead.c
  2026-02-02  6:06 fsverity speedup and memory usage optimization v5 Christoph Hellwig
  2026-02-02  6:06 ` [PATCH 01/11] fsverity: don't issue readahead for non-ENOENT errors from __filemap_get_folio Christoph Hellwig
  2026-02-02  6:06 ` [PATCH 02/11] readahead: push invalidate_lock out of page_cache_ra_unbounded Christoph Hellwig
@ 2026-02-02  6:06 ` Christoph Hellwig
  2026-02-02 13:32   ` Jan Kara
  2026-02-03  0:57   ` Theodore Tso
  2026-02-02  6:06 ` [PATCH 04/11] fsverity: kick off hash readahead at data I/O submission time Christoph Hellwig
                   ` (8 subsequent siblings)
  11 siblings, 2 replies; 28+ messages in thread
From: Christoph Hellwig @ 2026-02-02  6:06 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	Matthew Wilcox, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity

Keep all the read into pagecache code in a single file.

Signed-off-by: Christoph Hellwig <hch@lst.de>
---
 fs/ext4/ext4.h     |  4 ++--
 fs/ext4/inode.c    | 27 ---------------------------
 fs/ext4/readpage.c | 31 ++++++++++++++++++++++++++++++-
 3 files changed, 32 insertions(+), 30 deletions(-)

diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 56112f201cac..a8a448e20ef8 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -3735,8 +3735,8 @@ static inline void ext4_set_de_type(struct super_block *sb,
 }
 
 /* readpages.c */
-extern int ext4_mpage_readpages(struct inode *inode,
-		struct readahead_control *rac, struct folio *folio);
+int ext4_read_folio(struct file *file, struct folio *folio);
+void ext4_readahead(struct readahead_control *rac);
 extern int __init ext4_init_post_read_processing(void);
 extern void ext4_exit_post_read_processing(void);
 
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 8c2ef98fa530..e98954e7d0b3 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -3380,33 +3380,6 @@ static sector_t ext4_bmap(struct address_space *mapping, sector_t block)
 	return ret;
 }
 
-static int ext4_read_folio(struct file *file, struct folio *folio)
-{
-	int ret = -EAGAIN;
-	struct inode *inode = folio->mapping->host;
-
-	trace_ext4_read_folio(inode, folio);
-
-	if (ext4_has_inline_data(inode))
-		ret = ext4_readpage_inline(inode, folio);
-
-	if (ret == -EAGAIN)
-		return ext4_mpage_readpages(inode, NULL, folio);
-
-	return ret;
-}
-
-static void ext4_readahead(struct readahead_control *rac)
-{
-	struct inode *inode = rac->mapping->host;
-
-	/* If the file has inline data, no need to do readahead. */
-	if (ext4_has_inline_data(inode))
-		return;
-
-	ext4_mpage_readpages(inode, rac, NULL);
-}
-
 static void ext4_invalidate_folio(struct folio *folio, size_t offset,
 				size_t length)
 {
diff --git a/fs/ext4/readpage.c b/fs/ext4/readpage.c
index 267594ef0b2c..bf84952ebf94 100644
--- a/fs/ext4/readpage.c
+++ b/fs/ext4/readpage.c
@@ -45,6 +45,7 @@
 #include <linux/pagevec.h>
 
 #include "ext4.h"
+#include <trace/events/ext4.h>
 
 #define NUM_PREALLOC_POST_READ_CTXS	128
 
@@ -209,7 +210,7 @@ static inline loff_t ext4_readpage_limit(struct inode *inode)
 	return i_size_read(inode);
 }
 
-int ext4_mpage_readpages(struct inode *inode,
+static int ext4_mpage_readpages(struct inode *inode,
 		struct readahead_control *rac, struct folio *folio)
 {
 	struct bio *bio = NULL;
@@ -394,6 +395,34 @@ int ext4_mpage_readpages(struct inode *inode,
 	return 0;
 }
 
+int ext4_read_folio(struct file *file, struct folio *folio)
+{
+	int ret = -EAGAIN;
+	struct inode *inode = folio->mapping->host;
+
+	trace_ext4_read_folio(inode, folio);
+
+	if (ext4_has_inline_data(inode))
+		ret = ext4_readpage_inline(inode, folio);
+
+	if (ret == -EAGAIN)
+		return ext4_mpage_readpages(inode, NULL, folio);
+
+	return ret;
+}
+
+void ext4_readahead(struct readahead_control *rac)
+{
+	struct inode *inode = rac->mapping->host;
+
+	/* If the file has inline data, no need to do readahead. */
+	if (ext4_has_inline_data(inode))
+		return;
+
+	ext4_mpage_readpages(inode, rac, NULL);
+}
+
+
 int __init ext4_init_post_read_processing(void)
 {
 	bio_post_read_ctx_cache = KMEM_CACHE(bio_post_read_ctx, SLAB_RECLAIM_ACCOUNT);
-- 
2.47.3


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

* [PATCH 04/11] fsverity: kick off hash readahead at data I/O submission time
  2026-02-02  6:06 fsverity speedup and memory usage optimization v5 Christoph Hellwig
                   ` (2 preceding siblings ...)
  2026-02-02  6:06 ` [PATCH 03/11] ext4: move ->read_folio and ->readahead to readahead.c Christoph Hellwig
@ 2026-02-02  6:06 ` Christoph Hellwig
  2026-02-02  6:06 ` [PATCH 05/11] fsverity: deconstify the inode pointer in struct fsverity_info Christoph Hellwig
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 28+ messages in thread
From: Christoph Hellwig @ 2026-02-02  6:06 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	Matthew Wilcox, 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>
Acked-by: David Sterba <dsterba@suse.com> [btrfs]
---
 fs/btrfs/verity.c         |  4 +-
 fs/ext4/readpage.c        | 17 ++++++---
 fs/ext4/verity.c          | 13 +++++--
 fs/f2fs/data.c            | 17 ++++++---
 fs/f2fs/verity.c          | 13 +++++--
 fs/verity/pagecache.c     | 44 +++++++++++++++-------
 fs/verity/read_metadata.c | 19 +++++++---
 fs/verity/verify.c        | 77 ++++++++++++++++++++++++++-------------
 include/linux/fsverity.h  | 30 +++++++++++----
 9 files changed, 161 insertions(+), 73 deletions(-)

diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c
index e7643c22a6bf..c152bef71e8b 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 bf84952ebf94..8438b14da37a 100644
--- a/fs/ext4/readpage.c
+++ b/fs/ext4/readpage.c
@@ -397,18 +397,20 @@ static int ext4_mpage_readpages(struct inode *inode,
 
 int ext4_read_folio(struct file *file, struct folio *folio)
 {
-	int ret = -EAGAIN;
 	struct inode *inode = folio->mapping->host;
+	int ret;
 
 	trace_ext4_read_folio(inode, folio);
 
-	if (ext4_has_inline_data(inode))
+	if (ext4_has_inline_data(inode)) {
 		ret = ext4_readpage_inline(inode, folio);
+		if (ret != -EAGAIN)
+			return ret;
+	}
 
-	if (ret == -EAGAIN)
-		return ext4_mpage_readpages(inode, NULL, folio);
-
-	return ret;
+	if (ext4_need_verity(inode, folio->index))
+		fsverity_readahead(inode, folio->index, folio_nr_pages(folio));
+	return ext4_mpage_readpages(inode, NULL, folio);
 }
 
 void ext4_readahead(struct readahead_control *rac)
@@ -419,6 +421,9 @@ void ext4_readahead(struct readahead_control *rac)
 	if (ext4_has_inline_data(inode))
 		return;
 
+	if (ext4_need_verity(inode, readahead_index(rac)))
+		fsverity_readahead(inode, readahead_index(rac),
+				   readahead_count(rac));
 	ext4_mpage_readpages(inode, rac, NULL);
 }
 
diff --git a/fs/ext4/verity.c b/fs/ext4/verity.c
index a071860ad36a..54ae4d4a176c 100644
--- a/fs/ext4/verity.c
+++ b/fs/ext4/verity.c
@@ -358,11 +358,17 @@ 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)
 {
 	index += ext4_verity_metadata_pos(inode) >> PAGE_SHIFT;
-	return generic_read_merkle_tree_page(inode, index, num_ra_pages);
+	return generic_read_merkle_tree_page(inode, 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,
@@ -380,5 +386,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..58d8a311ef2c 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -2458,7 +2458,7 @@ static int f2fs_mpage_readpages(struct inode *inode,
 static int f2fs_read_data_folio(struct file *file, struct folio *folio)
 {
 	struct inode *inode = folio->mapping->host;
-	int ret = -EAGAIN;
+	int ret;
 
 	trace_f2fs_readpage(folio, DATA);
 
@@ -2468,11 +2468,15 @@ static int f2fs_read_data_folio(struct file *file, struct folio *folio)
 	}
 
 	/* If the file has inline data, try to read it directly */
-	if (f2fs_has_inline_data(inode))
+	if (f2fs_has_inline_data(inode)) {
 		ret = f2fs_read_inline_data(inode, folio);
-	if (ret == -EAGAIN)
-		ret = f2fs_mpage_readpages(inode, NULL, folio);
-	return ret;
+		if (ret != -EAGAIN)
+			return ret;
+	}
+
+	if (f2fs_need_verity(inode, folio->index))
+		fsverity_readahead(inode, folio->index, folio_nr_pages(folio));
+	return f2fs_mpage_readpages(inode, NULL, folio);
 }
 
 static void f2fs_readahead(struct readahead_control *rac)
@@ -2488,6 +2492,9 @@ static void f2fs_readahead(struct readahead_control *rac)
 	if (f2fs_has_inline_data(inode))
 		return;
 
+	if (f2fs_need_verity(inode, readahead_index(rac)))
+		fsverity_readahead(inode, readahead_index(rac),
+				   readahead_count(rac));
 	f2fs_mpage_readpages(inode, rac, NULL);
 }
 
diff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c
index d37e584423af..628e8eafa96a 100644
--- a/fs/f2fs/verity.c
+++ b/fs/f2fs/verity.c
@@ -256,11 +256,17 @@ 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)
 {
 	index += f2fs_verity_metadata_pos(inode) >> PAGE_SHIFT;
-	return generic_read_merkle_tree_page(inode, index, num_ra_pages);
+	return generic_read_merkle_tree_page(inode, 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,
@@ -278,5 +284,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/pagecache.c b/fs/verity/pagecache.c
index 8e0d6fde802f..50b517ed6be0 100644
--- a/fs/verity/pagecache.c
+++ b/fs/verity/pagecache.c
@@ -16,27 +16,43 @@
  * to ->read_merkle_tree_page to the actual index where the Merkle tree is
  * stored in the page cache for @inode.
  */
-struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index,
-					   unsigned long num_ra_pages)
+struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index)
 {
 	struct folio *folio;
 
+	folio = read_mapping_folio(inode->i_mapping, index, NULL);
+	if (IS_ERR(folio))
+		return ERR_CAST(folio);
+	return folio_file_page(folio, index);
+}
+EXPORT_SYMBOL_GPL(generic_read_merkle_tree_page);
+
+/**
+ * generic_readahead_merkle_tree() - generic ->readahead_merkle_tree helper
+ * @inode:	inode containing the Merkle tree
+ * @index:	0-based index of the first Merkle tree page to read ahead in the
+ *		inode
+ * @nr_pages:	the number of Merkle tree pages that should be read ahead
+ *
+ * The caller needs to adjust @index from the Merkle-tree relative index passed
+ * to ->read_merkle_tree_page to the actual index where the Merkle tree is
+ * stored in the page cache for @inode.
+ */
+void generic_readahead_merkle_tree(struct inode *inode, pgoff_t index,
+				   unsigned long nr_pages)
+{
+	struct folio *folio;
+
+	lockdep_assert_held_read(&inode->i_mapping->invalidate_lock);
+
 	folio = __filemap_get_folio(inode->i_mapping, index, FGP_ACCESSED, 0);
 	if (folio == ERR_PTR(-ENOENT) ||
 	    (!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) {
-			filemap_invalidate_lock_shared(inode->i_mapping);
-			page_cache_ra_unbounded(&ractl, num_ra_pages, 0);
-			filemap_invalidate_unlock_shared(inode->i_mapping);
-		}
-		folio = read_mapping_folio(inode->i_mapping, index, NULL);
+		page_cache_ra_unbounded(&ractl, nr_pages, 0);
 	}
-	if (IS_ERR(folio))
-		return ERR_CAST(folio);
-	return folio_file_page(folio, index);
+	if (!IS_ERR(folio))
+		folio_put(folio);
 }
-EXPORT_SYMBOL_GPL(generic_read_merkle_tree_page);
+EXPORT_SYMBOL_GPL(generic_readahead_merkle_tree);
diff --git a/fs/verity/read_metadata.c b/fs/verity/read_metadata.c
index cba5d6af4e04..2807d44dc6bb 100644
--- a/fs/verity/read_metadata.c
+++ b/fs/verity/read_metadata.c
@@ -28,24 +28,33 @@ 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;
 
+	/*
+	 * Kick off readahead for the range we are going to read to ensure a
+	 * single large sequential read instead of lots of small ones.
+	 */
+	if (inode->i_sb->s_vop->readahead_merkle_tree) {
+		filemap_invalidate_lock_shared(inode->i_mapping);
+		inode->i_sb->s_vop->readahead_merkle_tree(inode, index,
+				last_index - index + 1);
+		filemap_invalidate_unlock_shared(inode->i_mapping);
+	}
+
 	/*
 	 * 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 86067c8b40cf..dac004f2a1a0 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,50 @@ struct fsverity_verification_context {
 
 static struct workqueue_struct *fsverity_read_workqueue;
 
+/**
+ * fsverity_readahead() - kick off readahead on fsverity hashes
+ * @inode:		inode that is being read
+ * @index:		first file data page index that is being read
+ * @nr_pages:		number of file data pages to be read
+ *
+ * Start readahead on the fsverity hashes that are needed to verity the file
+ * data in the range from @index to @inode + @nr_pages.
+ *
+ * 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, pgoff_t index,
+			unsigned long nr_pages)
+{
+	const struct fsverity_info *vi = *fsverity_info_addr(inode);
+	const struct merkle_tree_params *params = &vi->tree_params;
+	u64 start_hidx = (u64)index << params->log_blocks_per_page;
+	u64 end_hidx =
+		(((u64)index + nr_pages) << params->log_blocks_per_page) - 1;
+	int level;
+
+	if (!inode->i_sb->s_vop->readahead_merkle_tree)
+		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;
+		pgoff_t start_idx = (level_start + next_start_hidx) >>
+				params->log_blocks_per_page;
+		pgoff_t 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 +158,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 +243,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,14 +314,12 @@ 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)
 {
 	struct fsverity_info *vi = *fsverity_info_addr(inode);
 
 	ctx->inode = inode;
 	ctx->vi = vi;
-	ctx->max_ra_pages = max_ra_pages;
 	ctx->num_pending = 0;
 	if (vi->tree_params.hash_alg->algo_id == HASH_ALGO_SHA256 &&
 	    sha256_finup_2x_is_optimized())
@@ -322,8 +362,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);
@@ -373,7 +412,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))
@@ -403,22 +442,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 8ddaa87fece3..580234d8ed2f 100644
--- a/include/linux/fsverity.h
+++ b/include/linux/fsverity.h
@@ -97,10 +97,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.
@@ -110,8 +106,23 @@ 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);
+
+	/**
+	 * Perform readahead of a Merkle tree for the given inode.
+	 *
+	 * @inode: the inode
+	 * @index: 0-based index of the first page within the Merkle tree
+	 * @nr_pages: number of pages to be read ahead.
+	 *
+	 * This can be called at any time on an open verity file.  It may be
+	 * called by multiple processes concurrently, even with the same range.
+	 *
+	 * Optional method so that ->read_merkle_tree_page preferably finds
+	 * cached data instead of issuing dependent I/O.
+	 */
+	void (*readahead_merkle_tree)(struct inode *inode, pgoff_t index,
+			unsigned long nr_pages);
 
 	/**
 	 * Write a Merkle tree block to the given file.
@@ -308,8 +319,11 @@ static inline int fsverity_file_open(struct inode *inode, struct file *filp)
 }
 
 void fsverity_cleanup_inode(struct inode *inode);
+void fsverity_readahead(struct inode *inode, pgoff_t index,
+			unsigned long nr_pages);
 
-struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index,
-					   unsigned long num_ra_pages);
+struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index);
+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] 28+ messages in thread

* [PATCH 05/11] fsverity: deconstify the inode pointer in struct fsverity_info
  2026-02-02  6:06 fsverity speedup and memory usage optimization v5 Christoph Hellwig
                   ` (3 preceding siblings ...)
  2026-02-02  6:06 ` [PATCH 04/11] fsverity: kick off hash readahead at data I/O submission time Christoph Hellwig
@ 2026-02-02  6:06 ` Christoph Hellwig
  2026-02-02  6:06 ` [PATCH 06/11] fsverity: push out fsverity_info lookup Christoph Hellwig
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 28+ messages in thread
From: Christoph Hellwig @ 2026-02-02  6:06 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	Matthew Wilcox, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity, Darrick J. Wong

A lot of file system code expects a non-const inode pointer.  Dropping
the const qualifier here allows using the inode pointer in
verify_data_block and prepares for further argument reductions.

Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
 fs/verity/fsverity_private.h | 4 ++--
 fs/verity/open.c             | 2 +-
 fs/verity/verify.c           | 5 +++--
 3 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/fs/verity/fsverity_private.h b/fs/verity/fsverity_private.h
index dd20b138d452..f9f3936b0a89 100644
--- a/fs/verity/fsverity_private.h
+++ b/fs/verity/fsverity_private.h
@@ -73,7 +73,7 @@ struct fsverity_info {
 	struct merkle_tree_params tree_params;
 	u8 root_hash[FS_VERITY_MAX_DIGEST_SIZE];
 	u8 file_digest[FS_VERITY_MAX_DIGEST_SIZE];
-	const struct inode *inode;
+	struct inode *inode;
 	unsigned long *hash_block_verified;
 };
 
@@ -124,7 +124,7 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params,
 				     unsigned int log_blocksize,
 				     const u8 *salt, size_t salt_size);
 
-struct fsverity_info *fsverity_create_info(const struct inode *inode,
+struct fsverity_info *fsverity_create_info(struct inode *inode,
 					   struct fsverity_descriptor *desc);
 
 void fsverity_set_info(struct inode *inode, struct fsverity_info *vi);
diff --git a/fs/verity/open.c b/fs/verity/open.c
index 090cb77326ee..128502cf0a23 100644
--- a/fs/verity/open.c
+++ b/fs/verity/open.c
@@ -175,7 +175,7 @@ static void compute_file_digest(const struct fsverity_hash_alg *hash_alg,
  * appended builtin signature), and check the signature if present.  The
  * fsverity_descriptor must have already undergone basic validation.
  */
-struct fsverity_info *fsverity_create_info(const struct inode *inode,
+struct fsverity_info *fsverity_create_info(struct inode *inode,
 					   struct fsverity_descriptor *desc)
 {
 	struct fsverity_info *vi;
diff --git a/fs/verity/verify.c b/fs/verity/verify.c
index dac004f2a1a0..0de55c8e4217 100644
--- a/fs/verity/verify.c
+++ b/fs/verity/verify.c
@@ -157,9 +157,10 @@ 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,
+static bool verify_data_block(struct fsverity_info *vi,
 			      const struct fsverity_pending_block *dblock)
 {
+	struct inode *inode = vi->inode;
 	const u64 data_pos = dblock->pos;
 	const struct merkle_tree_params *params = &vi->tree_params;
 	const unsigned int hsize = params->digest_size;
@@ -362,7 +363,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]))
+		if (!verify_data_block(vi, &ctx->pending_blocks[i]))
 			return false;
 	}
 	fsverity_clear_pending_blocks(ctx);
-- 
2.47.3


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

* [PATCH 06/11] fsverity: push out fsverity_info lookup
  2026-02-02  6:06 fsverity speedup and memory usage optimization v5 Christoph Hellwig
                   ` (4 preceding siblings ...)
  2026-02-02  6:06 ` [PATCH 05/11] fsverity: deconstify the inode pointer in struct fsverity_info Christoph Hellwig
@ 2026-02-02  6:06 ` Christoph Hellwig
  2026-02-02  6:06 ` [PATCH 07/11] fs: consolidate fsverity_info lookup in buffer.c Christoph Hellwig
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 28+ messages in thread
From: Christoph Hellwig @ 2026-02-02  6:06 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	Matthew Wilcox, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity, Darrick J. Wong

Pass a struct fsverity_info to the verification and readahead helpers,
and push the lookup into the callers.  Right now this is a very
dumb almost mechanic move that open codes a lot of fsverity_info_addr()
calls int the file systems.  The subsequent patches will clean this up.

This prepares for reducing the number of fsverity_info lookups, which
will allow to amortize them better when using a more expensive lookup
method.

Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
Acked-by: David Sterba <dsterba@suse.com> [btrfs]
---
 fs/btrfs/extent_io.c     |  3 ++-
 fs/buffer.c              |  4 +++-
 fs/ext4/readpage.c       | 14 +++++++++-----
 fs/f2fs/compress.c       |  4 +++-
 fs/f2fs/data.c           | 19 +++++++++++++------
 fs/verity/verify.c       | 24 ++++++++++++------------
 include/linux/fsverity.h | 32 ++++++++++++++++++++++----------
 7 files changed, 64 insertions(+), 36 deletions(-)

diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index a4b74023618d..21430b7d8f27 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -484,7 +484,8 @@ static bool btrfs_verify_folio(struct folio *folio, u64 start, u32 len)
 	    btrfs_folio_test_uptodate(fs_info, folio, start, len) ||
 	    start >= i_size_read(folio->mapping->host))
 		return true;
-	return fsverity_verify_folio(folio);
+	return fsverity_verify_folio(*fsverity_info_addr(folio->mapping->host),
+			folio);
 }
 
 static void end_folio_read(struct folio *folio, bool uptodate, u64 start, u32 len)
diff --git a/fs/buffer.c b/fs/buffer.c
index 838c0c571022..3982253b6805 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -309,9 +309,11 @@ static void verify_bh(struct work_struct *work)
 	struct postprocess_bh_ctx *ctx =
 		container_of(work, struct postprocess_bh_ctx, work);
 	struct buffer_head *bh = ctx->bh;
+	struct inode *inode = bh->b_folio->mapping->host;
 	bool valid;
 
-	valid = fsverity_verify_blocks(bh->b_folio, bh->b_size, bh_offset(bh));
+	valid = fsverity_verify_blocks(*fsverity_info_addr(inode), bh->b_folio,
+				       bh->b_size, bh_offset(bh));
 	end_buffer_async_read(bh, valid);
 	kfree(ctx);
 }
diff --git a/fs/ext4/readpage.c b/fs/ext4/readpage.c
index 8438b14da37a..823d67e98c70 100644
--- a/fs/ext4/readpage.c
+++ b/fs/ext4/readpage.c
@@ -97,6 +97,7 @@ static void verity_work(struct work_struct *work)
 	struct bio_post_read_ctx *ctx =
 		container_of(work, struct bio_post_read_ctx, work);
 	struct bio *bio = ctx->bio;
+	struct inode *inode = bio_first_folio_all(bio)->mapping->host;
 
 	/*
 	 * fsverity_verify_bio() may call readahead() again, and although verity
@@ -109,7 +110,7 @@ static void verity_work(struct work_struct *work)
 	mempool_free(ctx, bio_post_read_ctx_pool);
 	bio->bi_private = NULL;
 
-	fsverity_verify_bio(bio);
+	fsverity_verify_bio(*fsverity_info_addr(inode), bio);
 
 	__read_end_io(bio);
 }
@@ -331,7 +332,9 @@ static int ext4_mpage_readpages(struct inode *inode,
 					  folio_size(folio));
 			if (first_hole == 0) {
 				if (ext4_need_verity(inode, folio->index) &&
-				    !fsverity_verify_folio(folio))
+				    !fsverity_verify_folio(
+						*fsverity_info_addr(inode),
+						folio))
 					goto set_error_page;
 				folio_end_read(folio, true);
 				continue;
@@ -409,7 +412,8 @@ int ext4_read_folio(struct file *file, struct folio *folio)
 	}
 
 	if (ext4_need_verity(inode, folio->index))
-		fsverity_readahead(inode, folio->index, folio_nr_pages(folio));
+		fsverity_readahead(*fsverity_info_addr(inode), folio->index,
+				   folio_nr_pages(folio));
 	return ext4_mpage_readpages(inode, NULL, folio);
 }
 
@@ -422,8 +426,8 @@ void ext4_readahead(struct readahead_control *rac)
 		return;
 
 	if (ext4_need_verity(inode, readahead_index(rac)))
-		fsverity_readahead(inode, readahead_index(rac),
-				   readahead_count(rac));
+		fsverity_readahead(*fsverity_info_addr(inode),
+				   readahead_index(rac), readahead_count(rac));
 	ext4_mpage_readpages(inode, rac, NULL);
 }
 
diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c
index 40a62f1dee4d..3de4a7e66959 100644
--- a/fs/f2fs/compress.c
+++ b/fs/f2fs/compress.c
@@ -1814,7 +1814,9 @@ static void f2fs_verify_cluster(struct work_struct *work)
 		if (!rpage)
 			continue;
 
-		if (fsverity_verify_page(rpage))
+		if (fsverity_verify_page(
+				*fsverity_info_addr(rpage->mapping->host),
+				rpage))
 			SetPageUptodate(rpage);
 		else
 			ClearPageUptodate(rpage);
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index 58d8a311ef2c..3593208c99db 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -185,15 +185,19 @@ static void f2fs_verify_bio(struct work_struct *work)
 
 		bio_for_each_folio_all(fi, bio) {
 			struct folio *folio = fi.folio;
+			struct fsverity_info *vi =
+				*fsverity_info_addr(folio->mapping->host);
 
 			if (!f2fs_is_compressed_page(folio) &&
-			    !fsverity_verify_page(&folio->page)) {
+			    !fsverity_verify_page(vi, &folio->page)) {
 				bio->bi_status = BLK_STS_IOERR;
 				break;
 			}
 		}
 	} else {
-		fsverity_verify_bio(bio);
+		struct inode *inode = bio_first_folio_all(bio)->mapping->host;
+
+		fsverity_verify_bio(*fsverity_info_addr(inode), bio);
 	}
 
 	f2fs_finish_read_bio(bio, true);
@@ -2121,7 +2125,9 @@ static int f2fs_read_single_page(struct inode *inode, struct folio *folio,
 zero_out:
 		folio_zero_segment(folio, 0, folio_size(folio));
 		if (f2fs_need_verity(inode, index) &&
-		    !fsverity_verify_folio(folio)) {
+		    !fsverity_verify_folio(
+				*fsverity_info_addr(folio->mapping->host),
+				folio)) {
 			ret = -EIO;
 			goto out;
 		}
@@ -2475,7 +2481,8 @@ static int f2fs_read_data_folio(struct file *file, struct folio *folio)
 	}
 
 	if (f2fs_need_verity(inode, folio->index))
-		fsverity_readahead(inode, folio->index, folio_nr_pages(folio));
+		fsverity_readahead(*fsverity_info_addr(inode), folio->index,
+				   folio_nr_pages(folio));
 	return f2fs_mpage_readpages(inode, NULL, folio);
 }
 
@@ -2493,8 +2500,8 @@ static void f2fs_readahead(struct readahead_control *rac)
 		return;
 
 	if (f2fs_need_verity(inode, readahead_index(rac)))
-		fsverity_readahead(inode, readahead_index(rac),
-				   readahead_count(rac));
+		fsverity_readahead(*fsverity_info_addr(inode),
+				   readahead_index(rac), readahead_count(rac));
 	f2fs_mpage_readpages(inode, rac, NULL);
 }
 
diff --git a/fs/verity/verify.c b/fs/verity/verify.c
index 0de55c8e4217..cf4c00273c16 100644
--- a/fs/verity/verify.c
+++ b/fs/verity/verify.c
@@ -39,7 +39,7 @@ static struct workqueue_struct *fsverity_read_workqueue;
 
 /**
  * fsverity_readahead() - kick off readahead on fsverity hashes
- * @inode:		inode that is being read
+ * @vi:			fsverity_info for the inode to be read
  * @index:		first file data page index that is being read
  * @nr_pages:		number of file data pages to be read
  *
@@ -50,10 +50,10 @@ static struct workqueue_struct *fsverity_read_workqueue;
  * ensure that the hashes are already cached on completion of the file data
  * read if possible.
  */
-void fsverity_readahead(struct inode *inode, pgoff_t index,
+void fsverity_readahead(struct fsverity_info *vi, pgoff_t index,
 			unsigned long nr_pages)
 {
-	const struct fsverity_info *vi = *fsverity_info_addr(inode);
+	struct inode *inode = vi->inode;
 	const struct merkle_tree_params *params = &vi->tree_params;
 	u64 start_hidx = (u64)index << params->log_blocks_per_page;
 	u64 end_hidx =
@@ -315,11 +315,9 @@ static bool verify_data_block(struct fsverity_info *vi,
 
 static void
 fsverity_init_verification_context(struct fsverity_verification_context *ctx,
-				   struct inode *inode)
+				   struct fsverity_info *vi)
 {
-	struct fsverity_info *vi = *fsverity_info_addr(inode);
-
-	ctx->inode = inode;
+	ctx->inode = vi->inode;
 	ctx->vi = vi;
 	ctx->num_pending = 0;
 	if (vi->tree_params.hash_alg->algo_id == HASH_ALGO_SHA256 &&
@@ -399,6 +397,7 @@ static bool fsverity_add_data_blocks(struct fsverity_verification_context *ctx,
 
 /**
  * fsverity_verify_blocks() - verify data in a folio
+ * @vi: fsverity_info for the inode to be read
  * @folio: the folio containing the data to verify
  * @len: the length of the data to verify in the folio
  * @offset: the offset of the data to verify in the folio
@@ -409,11 +408,12 @@ static bool fsverity_add_data_blocks(struct fsverity_verification_context *ctx,
  *
  * Return: %true if the data is valid, else %false.
  */
-bool fsverity_verify_blocks(struct folio *folio, size_t len, size_t offset)
+bool fsverity_verify_blocks(struct fsverity_info *vi, struct folio *folio,
+			    size_t len, size_t offset)
 {
 	struct fsverity_verification_context ctx;
 
-	fsverity_init_verification_context(&ctx, folio->mapping->host);
+	fsverity_init_verification_context(&ctx, vi);
 
 	if (fsverity_add_data_blocks(&ctx, folio, len, offset) &&
 	    fsverity_verify_pending_blocks(&ctx))
@@ -426,6 +426,7 @@ EXPORT_SYMBOL_GPL(fsverity_verify_blocks);
 #ifdef CONFIG_BLOCK
 /**
  * fsverity_verify_bio() - verify a 'read' bio that has just completed
+ * @vi: fsverity_info for the inode to be read
  * @bio: the bio to verify
  *
  * Verify the bio's data against the file's Merkle tree.  All bio data segments
@@ -438,13 +439,12 @@ EXPORT_SYMBOL_GPL(fsverity_verify_blocks);
  * filesystems) must instead call fsverity_verify_page() directly on each page.
  * All filesystems must also call fsverity_verify_page() on holes.
  */
-void fsverity_verify_bio(struct bio *bio)
+void fsverity_verify_bio(struct fsverity_info *vi, struct bio *bio)
 {
-	struct inode *inode = bio_first_folio_all(bio)->mapping->host;
 	struct fsverity_verification_context ctx;
 	struct folio_iter fi;
 
-	fsverity_init_verification_context(&ctx, inode);
+	fsverity_init_verification_context(&ctx, vi);
 
 	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 580234d8ed2f..ab7244f7d172 100644
--- a/include/linux/fsverity.h
+++ b/include/linux/fsverity.h
@@ -197,12 +197,20 @@ int fsverity_ioctl_read_metadata(struct file *filp, const void __user *uarg);
 
 /* verify.c */
 
-bool fsverity_verify_blocks(struct folio *folio, size_t len, size_t offset);
-void fsverity_verify_bio(struct bio *bio);
+bool fsverity_verify_blocks(struct fsverity_info *vi, struct folio *folio,
+			    size_t len, size_t offset);
+void fsverity_verify_bio(struct fsverity_info *vi, struct bio *bio);
 void fsverity_enqueue_verify_work(struct work_struct *work);
 
 #else /* !CONFIG_FS_VERITY */
 
+/*
+ * Provide a stub to allow code using this to compile.  All callsites should be
+ * guarded by compiler dead code elimination, and this forces a link error if
+ * not.
+ */
+struct fsverity_info **fsverity_info_addr(const struct inode *inode);
+
 static inline struct fsverity_info *fsverity_get_info(const struct inode *inode)
 {
 	return NULL;
@@ -251,14 +259,16 @@ static inline int fsverity_ioctl_read_metadata(struct file *filp,
 
 /* verify.c */
 
-static inline bool fsverity_verify_blocks(struct folio *folio, size_t len,
+static inline bool fsverity_verify_blocks(struct fsverity_info *vi,
+					  struct folio *folio, size_t len,
 					  size_t offset)
 {
 	WARN_ON_ONCE(1);
 	return false;
 }
 
-static inline void fsverity_verify_bio(struct bio *bio)
+static inline void fsverity_verify_bio(struct fsverity_info *vi,
+				       struct bio *bio)
 {
 	WARN_ON_ONCE(1);
 }
@@ -270,14 +280,16 @@ static inline void fsverity_enqueue_verify_work(struct work_struct *work)
 
 #endif	/* !CONFIG_FS_VERITY */
 
-static inline bool fsverity_verify_folio(struct folio *folio)
+static inline bool fsverity_verify_folio(struct fsverity_info *vi,
+					 struct folio *folio)
 {
-	return fsverity_verify_blocks(folio, folio_size(folio), 0);
+	return fsverity_verify_blocks(vi, folio, folio_size(folio), 0);
 }
 
-static inline bool fsverity_verify_page(struct page *page)
+static inline bool fsverity_verify_page(struct fsverity_info *vi,
+					struct page *page)
 {
-	return fsverity_verify_blocks(page_folio(page), PAGE_SIZE, 0);
+	return fsverity_verify_blocks(vi, page_folio(page), PAGE_SIZE, 0);
 }
 
 /**
@@ -319,8 +331,8 @@ static inline int fsverity_file_open(struct inode *inode, struct file *filp)
 }
 
 void fsverity_cleanup_inode(struct inode *inode);
-void fsverity_readahead(struct inode *inode, pgoff_t index,
-			unsigned long nr_pages);
+void fsverity_readahead(struct fsverity_info *vi, pgoff_t index,
+		unsigned long nr_pages);
 
 struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index);
 void generic_readahead_merkle_tree(struct inode *inode, pgoff_t index,
-- 
2.47.3


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

* [PATCH 07/11] fs: consolidate fsverity_info lookup in buffer.c
  2026-02-02  6:06 fsverity speedup and memory usage optimization v5 Christoph Hellwig
                   ` (5 preceding siblings ...)
  2026-02-02  6:06 ` [PATCH 06/11] fsverity: push out fsverity_info lookup Christoph Hellwig
@ 2026-02-02  6:06 ` Christoph Hellwig
  2026-02-02 13:38   ` Jan Kara
  2026-02-02  6:06 ` [PATCH 08/11] ext4: consolidate fsverity_info lookup Christoph Hellwig
                   ` (4 subsequent siblings)
  11 siblings, 1 reply; 28+ messages in thread
From: Christoph Hellwig @ 2026-02-02  6:06 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	Matthew Wilcox, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity, Darrick J. Wong

Look up the fsverity_info once in end_buffer_async_read_io, and then
pass it along to the I/O completion workqueue in
struct postprocess_bh_ctx.

This amortizes the lookup better once it becomes less efficient.

Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
 fs/buffer.c | 27 +++++++++++----------------
 1 file changed, 11 insertions(+), 16 deletions(-)

diff --git a/fs/buffer.c b/fs/buffer.c
index 3982253b6805..f4b3297ef1b1 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -302,6 +302,7 @@ static void end_buffer_async_read(struct buffer_head *bh, int uptodate)
 struct postprocess_bh_ctx {
 	struct work_struct work;
 	struct buffer_head *bh;
+	struct fsverity_info *vi;
 };
 
 static void verify_bh(struct work_struct *work)
@@ -309,25 +310,14 @@ static void verify_bh(struct work_struct *work)
 	struct postprocess_bh_ctx *ctx =
 		container_of(work, struct postprocess_bh_ctx, work);
 	struct buffer_head *bh = ctx->bh;
-	struct inode *inode = bh->b_folio->mapping->host;
 	bool valid;
 
-	valid = fsverity_verify_blocks(*fsverity_info_addr(inode), bh->b_folio,
-				       bh->b_size, bh_offset(bh));
+	valid = fsverity_verify_blocks(ctx->vi, bh->b_folio, bh->b_size,
+				       bh_offset(bh));
 	end_buffer_async_read(bh, valid);
 	kfree(ctx);
 }
 
-static bool need_fsverity(struct buffer_head *bh)
-{
-	struct folio *folio = bh->b_folio;
-	struct inode *inode = folio->mapping->host;
-
-	return fsverity_active(inode) &&
-		/* needed by ext4 */
-		folio->index < DIV_ROUND_UP(inode->i_size, PAGE_SIZE);
-}
-
 static void decrypt_bh(struct work_struct *work)
 {
 	struct postprocess_bh_ctx *ctx =
@@ -337,7 +327,7 @@ static void decrypt_bh(struct work_struct *work)
 
 	err = fscrypt_decrypt_pagecache_blocks(bh->b_folio, bh->b_size,
 					       bh_offset(bh));
-	if (err == 0 && need_fsverity(bh)) {
+	if (err == 0 && ctx->vi) {
 		/*
 		 * We use different work queues for decryption and for verity
 		 * because verity may require reading metadata pages that need
@@ -359,15 +349,20 @@ static void end_buffer_async_read_io(struct buffer_head *bh, int uptodate)
 {
 	struct inode *inode = bh->b_folio->mapping->host;
 	bool decrypt = fscrypt_inode_uses_fs_layer_crypto(inode);
-	bool verify = need_fsverity(bh);
+	struct fsverity_info *vi = NULL;
+
+	/* needed by ext4 */
+	if (bh->b_folio->index < DIV_ROUND_UP(inode->i_size, PAGE_SIZE))
+		vi = fsverity_get_info(inode);
 
 	/* Decrypt (with fscrypt) and/or verify (with fsverity) if needed. */
-	if (uptodate && (decrypt || verify)) {
+	if (uptodate && (decrypt || vi)) {
 		struct postprocess_bh_ctx *ctx =
 			kmalloc(sizeof(*ctx), GFP_ATOMIC);
 
 		if (ctx) {
 			ctx->bh = bh;
+			ctx->vi = vi;
 			if (decrypt) {
 				INIT_WORK(&ctx->work, decrypt_bh);
 				fscrypt_enqueue_decrypt_work(&ctx->work);
-- 
2.47.3


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

* [PATCH 08/11] ext4: consolidate fsverity_info lookup
  2026-02-02  6:06 fsverity speedup and memory usage optimization v5 Christoph Hellwig
                   ` (6 preceding siblings ...)
  2026-02-02  6:06 ` [PATCH 07/11] fs: consolidate fsverity_info lookup in buffer.c Christoph Hellwig
@ 2026-02-02  6:06 ` Christoph Hellwig
  2026-02-02  6:06 ` [PATCH 09/11] f2fs: " Christoph Hellwig
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 28+ messages in thread
From: Christoph Hellwig @ 2026-02-02  6:06 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	Matthew Wilcox, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity, Darrick J. Wong

Look up the fsverity_info once in ext4_mpage_readpages, and then use it
for the readahead, local verification of holes and pass it along to the
I/O completion workqueue in struct bio_post_read_ctx.

This amortizes the lookup better once it becomes less efficient.

Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
 fs/ext4/readpage.c | 46 ++++++++++++++++++++++------------------------
 1 file changed, 22 insertions(+), 24 deletions(-)

diff --git a/fs/ext4/readpage.c b/fs/ext4/readpage.c
index 823d67e98c70..09acca898c25 100644
--- a/fs/ext4/readpage.c
+++ b/fs/ext4/readpage.c
@@ -62,6 +62,7 @@ enum bio_post_read_step {
 
 struct bio_post_read_ctx {
 	struct bio *bio;
+	struct fsverity_info *vi;
 	struct work_struct work;
 	unsigned int cur_step;
 	unsigned int enabled_steps;
@@ -97,7 +98,7 @@ static void verity_work(struct work_struct *work)
 	struct bio_post_read_ctx *ctx =
 		container_of(work, struct bio_post_read_ctx, work);
 	struct bio *bio = ctx->bio;
-	struct inode *inode = bio_first_folio_all(bio)->mapping->host;
+	struct fsverity_info *vi = ctx->vi;
 
 	/*
 	 * fsverity_verify_bio() may call readahead() again, and although verity
@@ -110,7 +111,7 @@ static void verity_work(struct work_struct *work)
 	mempool_free(ctx, bio_post_read_ctx_pool);
 	bio->bi_private = NULL;
 
-	fsverity_verify_bio(*fsverity_info_addr(inode), bio);
+	fsverity_verify_bio(vi, bio);
 
 	__read_end_io(bio);
 }
@@ -174,22 +175,16 @@ static void mpage_end_io(struct bio *bio)
 	__read_end_io(bio);
 }
 
-static inline bool ext4_need_verity(const struct inode *inode, pgoff_t idx)
-{
-	return fsverity_active(inode) &&
-	       idx < DIV_ROUND_UP(inode->i_size, PAGE_SIZE);
-}
-
 static void ext4_set_bio_post_read_ctx(struct bio *bio,
 				       const struct inode *inode,
-				       pgoff_t first_idx)
+				       struct fsverity_info *vi)
 {
 	unsigned int post_read_steps = 0;
 
 	if (fscrypt_inode_uses_fs_layer_crypto(inode))
 		post_read_steps |= 1 << STEP_DECRYPT;
 
-	if (ext4_need_verity(inode, first_idx))
+	if (vi)
 		post_read_steps |= 1 << STEP_VERITY;
 
 	if (post_read_steps) {
@@ -198,6 +193,7 @@ static void ext4_set_bio_post_read_ctx(struct bio *bio,
 			mempool_alloc(bio_post_read_ctx_pool, GFP_NOFS);
 
 		ctx->bio = bio;
+		ctx->vi = vi;
 		ctx->enabled_steps = post_read_steps;
 		bio->bi_private = ctx;
 	}
@@ -211,7 +207,7 @@ static inline loff_t ext4_readpage_limit(struct inode *inode)
 	return i_size_read(inode);
 }
 
-static int ext4_mpage_readpages(struct inode *inode,
+static int ext4_mpage_readpages(struct inode *inode, struct fsverity_info *vi,
 		struct readahead_control *rac, struct folio *folio)
 {
 	struct bio *bio = NULL;
@@ -331,10 +327,7 @@ static int ext4_mpage_readpages(struct inode *inode,
 			folio_zero_segment(folio, first_hole << blkbits,
 					  folio_size(folio));
 			if (first_hole == 0) {
-				if (ext4_need_verity(inode, folio->index) &&
-				    !fsverity_verify_folio(
-						*fsverity_info_addr(inode),
-						folio))
+				if (vi && !fsverity_verify_folio(vi, folio))
 					goto set_error_page;
 				folio_end_read(folio, true);
 				continue;
@@ -362,7 +355,7 @@ static int ext4_mpage_readpages(struct inode *inode,
 					REQ_OP_READ, GFP_KERNEL);
 			fscrypt_set_bio_crypt_ctx(bio, inode, next_block,
 						  GFP_KERNEL);
-			ext4_set_bio_post_read_ctx(bio, inode, folio->index);
+			ext4_set_bio_post_read_ctx(bio, inode, vi);
 			bio->bi_iter.bi_sector = first_block << (blkbits - 9);
 			bio->bi_end_io = mpage_end_io;
 			if (rac)
@@ -401,6 +394,7 @@ static int ext4_mpage_readpages(struct inode *inode,
 int ext4_read_folio(struct file *file, struct folio *folio)
 {
 	struct inode *inode = folio->mapping->host;
+	struct fsverity_info *vi = NULL;
 	int ret;
 
 	trace_ext4_read_folio(inode, folio);
@@ -411,24 +405,28 @@ int ext4_read_folio(struct file *file, struct folio *folio)
 			return ret;
 	}
 
-	if (ext4_need_verity(inode, folio->index))
-		fsverity_readahead(*fsverity_info_addr(inode), folio->index,
-				   folio_nr_pages(folio));
-	return ext4_mpage_readpages(inode, NULL, folio);
+	if (folio->index < DIV_ROUND_UP(inode->i_size, PAGE_SIZE))
+		vi = fsverity_get_info(inode);
+	if (vi)
+		fsverity_readahead(vi, folio->index, folio_nr_pages(folio));
+	return ext4_mpage_readpages(inode, vi, NULL, folio);
 }
 
 void ext4_readahead(struct readahead_control *rac)
 {
 	struct inode *inode = rac->mapping->host;
+	struct fsverity_info *vi = NULL;
 
 	/* If the file has inline data, no need to do readahead. */
 	if (ext4_has_inline_data(inode))
 		return;
 
-	if (ext4_need_verity(inode, readahead_index(rac)))
-		fsverity_readahead(*fsverity_info_addr(inode),
-				   readahead_index(rac), readahead_count(rac));
-	ext4_mpage_readpages(inode, rac, NULL);
+	if (readahead_index(rac) < DIV_ROUND_UP(inode->i_size, PAGE_SIZE))
+		vi = fsverity_get_info(inode);
+	if (vi)
+		fsverity_readahead(vi, readahead_index(rac),
+				   readahead_count(rac));
+	ext4_mpage_readpages(inode, vi, rac, NULL);
 }
 
 
-- 
2.47.3


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

* [PATCH 09/11] f2fs: consolidate fsverity_info lookup
  2026-02-02  6:06 fsverity speedup and memory usage optimization v5 Christoph Hellwig
                   ` (7 preceding siblings ...)
  2026-02-02  6:06 ` [PATCH 08/11] ext4: consolidate fsverity_info lookup Christoph Hellwig
@ 2026-02-02  6:06 ` Christoph Hellwig
  2026-02-02  6:06 ` [PATCH 10/11] btrfs: " Christoph Hellwig
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 28+ messages in thread
From: Christoph Hellwig @ 2026-02-02  6:06 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	Matthew Wilcox, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity

Look up the fsverity_info once in f2fs_mpage_readpages, and then use it
for the readahead, local verification of holes and pass it along to the
I/O completion workqueue in struct bio_post_read_ctx.  Do the same
thing in f2fs_get_read_data_folio for reads that come from garbage
collection and other background activities.

This amortizes the lookup better once it becomes less efficient.

Signed-off-by: Christoph Hellwig <hch@lst.de>
---
 fs/f2fs/compress.c |  9 +++--
 fs/f2fs/data.c     | 88 +++++++++++++++++++++++++---------------------
 fs/f2fs/f2fs.h     |  9 ++---
 3 files changed, 54 insertions(+), 52 deletions(-)

diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c
index 3de4a7e66959..ef1225af2acf 100644
--- a/fs/f2fs/compress.c
+++ b/fs/f2fs/compress.c
@@ -1181,6 +1181,7 @@ int f2fs_prepare_compress_overwrite(struct inode *inode,
 		.cluster_idx = index >> F2FS_I(inode)->i_log_cluster_size,
 		.rpages = NULL,
 		.nr_rpages = 0,
+		.vi = NULL, /* can't write to fsverity files */
 	};
 
 	return prepare_compress_overwrite(&cc, pagep, index, fsdata);
@@ -1716,7 +1717,7 @@ struct decompress_io_ctx *f2fs_alloc_dic(struct compress_ctx *cc)
 	dic->nr_cpages = cc->nr_cpages;
 	refcount_set(&dic->refcnt, 1);
 	dic->failed = false;
-	dic->need_verity = f2fs_need_verity(cc->inode, start_idx);
+	dic->vi = cc->vi;
 
 	for (i = 0; i < dic->cluster_size; i++)
 		dic->rpages[i] = cc->rpages[i];
@@ -1814,9 +1815,7 @@ static void f2fs_verify_cluster(struct work_struct *work)
 		if (!rpage)
 			continue;
 
-		if (fsverity_verify_page(
-				*fsverity_info_addr(rpage->mapping->host),
-				rpage))
+		if (fsverity_verify_page(dic->vi, rpage))
 			SetPageUptodate(rpage);
 		else
 			ClearPageUptodate(rpage);
@@ -1835,7 +1834,7 @@ void f2fs_decompress_end_io(struct decompress_io_ctx *dic, bool failed,
 {
 	int i;
 
-	if (IS_ENABLED(CONFIG_FS_VERITY) && !failed && dic->need_verity) {
+	if (IS_ENABLED(CONFIG_FS_VERITY) && !failed && dic->vi) {
 		/*
 		 * Note that to avoid deadlocks, the verity work can't be done
 		 * on the decompression workqueue.  This is because verifying
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index 3593208c99db..41a5f2d21282 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -109,6 +109,7 @@ enum bio_post_read_step {
 struct bio_post_read_ctx {
 	struct bio *bio;
 	struct f2fs_sb_info *sbi;
+	struct fsverity_info *vi;
 	struct work_struct work;
 	unsigned int enabled_steps;
 	/*
@@ -165,6 +166,7 @@ static void f2fs_verify_bio(struct work_struct *work)
 		container_of(work, struct bio_post_read_ctx, work);
 	struct bio *bio = ctx->bio;
 	bool may_have_compressed_pages = (ctx->enabled_steps & STEP_DECOMPRESS);
+	struct fsverity_info *vi = ctx->vi;
 
 	/*
 	 * fsverity_verify_bio() may call readahead() again, and while verity
@@ -185,8 +187,6 @@ static void f2fs_verify_bio(struct work_struct *work)
 
 		bio_for_each_folio_all(fi, bio) {
 			struct folio *folio = fi.folio;
-			struct fsverity_info *vi =
-				*fsverity_info_addr(folio->mapping->host);
 
 			if (!f2fs_is_compressed_page(folio) &&
 			    !fsverity_verify_page(vi, &folio->page)) {
@@ -195,9 +195,7 @@ static void f2fs_verify_bio(struct work_struct *work)
 			}
 		}
 	} else {
-		struct inode *inode = bio_first_folio_all(bio)->mapping->host;
-
-		fsverity_verify_bio(*fsverity_info_addr(inode), bio);
+		fsverity_verify_bio(vi, bio);
 	}
 
 	f2fs_finish_read_bio(bio, true);
@@ -1040,9 +1038,9 @@ void f2fs_submit_page_write(struct f2fs_io_info *fio)
 	f2fs_up_write(&io->io_rwsem);
 }
 
-static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,
-				      unsigned nr_pages, blk_opf_t op_flag,
-				      pgoff_t first_idx, bool for_write)
+static struct bio *f2fs_grab_read_bio(struct inode *inode,
+		struct fsverity_info *vi, block_t blkaddr, unsigned nr_pages,
+		blk_opf_t op_flag, pgoff_t first_idx, bool for_write)
 {
 	struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
 	struct bio *bio;
@@ -1061,7 +1059,7 @@ static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,
 	if (fscrypt_inode_uses_fs_layer_crypto(inode))
 		post_read_steps |= STEP_DECRYPT;
 
-	if (f2fs_need_verity(inode, first_idx))
+	if (vi)
 		post_read_steps |= STEP_VERITY;
 
 	/*
@@ -1076,6 +1074,7 @@ static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,
 		ctx = mempool_alloc(bio_post_read_ctx_pool, GFP_NOFS);
 		ctx->bio = bio;
 		ctx->sbi = sbi;
+		ctx->vi = vi;
 		ctx->enabled_steps = post_read_steps;
 		ctx->fs_blkaddr = blkaddr;
 		ctx->decompression_attempted = false;
@@ -1087,15 +1086,15 @@ static struct bio *f2fs_grab_read_bio(struct inode *inode, block_t blkaddr,
 }
 
 /* This can handle encryption stuffs */
-static void f2fs_submit_page_read(struct inode *inode, struct folio *folio,
-				 block_t blkaddr, blk_opf_t op_flags,
-				 bool for_write)
+static void f2fs_submit_page_read(struct inode *inode, struct fsverity_info *vi,
+		struct folio *folio, block_t blkaddr, blk_opf_t op_flags,
+		bool for_write)
 {
 	struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
 	struct bio *bio;
 
-	bio = f2fs_grab_read_bio(inode, blkaddr, 1, op_flags,
-					folio->index, for_write);
+	bio = f2fs_grab_read_bio(inode, vi, blkaddr, 1, op_flags, folio->index,
+			for_write);
 
 	/* wait for GCed page writeback via META_MAPPING */
 	f2fs_wait_on_block_writeback(inode, blkaddr);
@@ -1197,6 +1196,14 @@ int f2fs_reserve_block(struct dnode_of_data *dn, pgoff_t index)
 	return err;
 }
 
+static inline struct fsverity_info *f2fs_need_verity(const struct inode *inode,
+		pgoff_t idx)
+{
+	if (idx < DIV_ROUND_UP(inode->i_size, PAGE_SIZE))
+		return fsverity_get_info(inode);
+	return NULL;
+}
+
 struct folio *f2fs_get_read_data_folio(struct inode *inode, pgoff_t index,
 		blk_opf_t op_flags, bool for_write, pgoff_t *next_pgofs)
 {
@@ -1262,8 +1269,8 @@ struct folio *f2fs_get_read_data_folio(struct inode *inode, pgoff_t index,
 		return folio;
 	}
 
-	f2fs_submit_page_read(inode, folio, dn.data_blkaddr,
-						op_flags, for_write);
+	f2fs_submit_page_read(inode, f2fs_need_verity(inode, folio->index),
+			folio, dn.data_blkaddr, op_flags, for_write);
 	return folio;
 
 put_err:
@@ -2067,12 +2074,10 @@ static inline blk_opf_t f2fs_ra_op_flags(struct readahead_control *rac)
 	return rac ? REQ_RAHEAD : 0;
 }
 
-static int f2fs_read_single_page(struct inode *inode, struct folio *folio,
-					unsigned nr_pages,
-					struct f2fs_map_blocks *map,
-					struct bio **bio_ret,
-					sector_t *last_block_in_bio,
-					struct readahead_control *rac)
+static int f2fs_read_single_page(struct inode *inode, struct fsverity_info *vi,
+		struct folio *folio, unsigned nr_pages,
+		struct f2fs_map_blocks *map, struct bio **bio_ret,
+		sector_t *last_block_in_bio, struct readahead_control *rac)
 {
 	struct bio *bio = *bio_ret;
 	const unsigned int blocksize = F2FS_BLKSIZE;
@@ -2124,10 +2129,7 @@ static int f2fs_read_single_page(struct inode *inode, struct folio *folio,
 	} else {
 zero_out:
 		folio_zero_segment(folio, 0, folio_size(folio));
-		if (f2fs_need_verity(inode, index) &&
-		    !fsverity_verify_folio(
-				*fsverity_info_addr(folio->mapping->host),
-				folio)) {
+		if (vi && !fsverity_verify_folio(vi, folio)) {
 			ret = -EIO;
 			goto out;
 		}
@@ -2149,7 +2151,7 @@ static int f2fs_read_single_page(struct inode *inode, struct folio *folio,
 		bio = NULL;
 	}
 	if (bio == NULL)
-		bio = f2fs_grab_read_bio(inode, block_nr, nr_pages,
+		bio = f2fs_grab_read_bio(inode, vi, block_nr, nr_pages,
 				f2fs_ra_op_flags(rac), index,
 				false);
 
@@ -2301,8 +2303,8 @@ int f2fs_read_multi_pages(struct compress_ctx *cc, struct bio **bio_ret,
 		}
 
 		if (!bio)
-			bio = f2fs_grab_read_bio(inode, blkaddr, nr_pages - i,
-					f2fs_ra_op_flags(rac),
+			bio = f2fs_grab_read_bio(inode, cc->vi, blkaddr,
+					nr_pages - i, f2fs_ra_op_flags(rac),
 					folio->index, for_write);
 
 		if (!bio_add_folio(bio, folio, blocksize, 0))
@@ -2342,7 +2344,7 @@ int f2fs_read_multi_pages(struct compress_ctx *cc, struct bio **bio_ret,
  * This function was originally taken from fs/mpage.c, and customized for f2fs.
  * Major change was from block_size == page_size in f2fs by default.
  */
-static int f2fs_mpage_readpages(struct inode *inode,
+static int f2fs_mpage_readpages(struct inode *inode, struct fsverity_info *vi,
 		struct readahead_control *rac, struct folio *folio)
 {
 	struct bio *bio = NULL;
@@ -2397,6 +2399,7 @@ static int f2fs_mpage_readpages(struct inode *inode,
 
 		/* there are remained compressed pages, submit them */
 		if (!f2fs_cluster_can_merge_page(&cc, index)) {
+			cc.vi = vi;
 			ret = f2fs_read_multi_pages(&cc, &bio,
 						max_nr_pages,
 						&last_block_in_bio,
@@ -2430,8 +2433,8 @@ static int f2fs_mpage_readpages(struct inode *inode,
 read_single_page:
 #endif
 
-		ret = f2fs_read_single_page(inode, folio, max_nr_pages, &map,
-					&bio, &last_block_in_bio, rac);
+		ret = f2fs_read_single_page(inode, vi, folio, max_nr_pages,
+					&map, &bio, &last_block_in_bio, rac);
 		if (ret) {
 #ifdef CONFIG_F2FS_FS_COMPRESSION
 set_error_page:
@@ -2447,6 +2450,7 @@ static int f2fs_mpage_readpages(struct inode *inode,
 		if (f2fs_compressed_file(inode)) {
 			/* last page */
 			if (nr_pages == 1 && !f2fs_cluster_is_empty(&cc)) {
+				cc.vi = vi;
 				ret = f2fs_read_multi_pages(&cc, &bio,
 							max_nr_pages,
 							&last_block_in_bio,
@@ -2464,6 +2468,7 @@ static int f2fs_mpage_readpages(struct inode *inode,
 static int f2fs_read_data_folio(struct file *file, struct folio *folio)
 {
 	struct inode *inode = folio->mapping->host;
+	struct fsverity_info *vi = NULL;
 	int ret;
 
 	trace_f2fs_readpage(folio, DATA);
@@ -2480,15 +2485,16 @@ static int f2fs_read_data_folio(struct file *file, struct folio *folio)
 			return ret;
 	}
 
-	if (f2fs_need_verity(inode, folio->index))
-		fsverity_readahead(*fsverity_info_addr(inode), folio->index,
-				   folio_nr_pages(folio));
-	return f2fs_mpage_readpages(inode, NULL, folio);
+	vi = f2fs_need_verity(inode, folio->index);
+	if (vi)
+		fsverity_readahead(vi, folio->index, folio_nr_pages(folio));
+	return f2fs_mpage_readpages(inode, vi, NULL, folio);
 }
 
 static void f2fs_readahead(struct readahead_control *rac)
 {
 	struct inode *inode = rac->mapping->host;
+	struct fsverity_info *vi = NULL;
 
 	trace_f2fs_readpages(inode, readahead_index(rac), readahead_count(rac));
 
@@ -2499,10 +2505,11 @@ static void f2fs_readahead(struct readahead_control *rac)
 	if (f2fs_has_inline_data(inode))
 		return;
 
-	if (f2fs_need_verity(inode, readahead_index(rac)))
-		fsverity_readahead(*fsverity_info_addr(inode),
-				   readahead_index(rac), readahead_count(rac));
-	f2fs_mpage_readpages(inode, rac, NULL);
+	vi = f2fs_need_verity(inode, readahead_index(rac));
+	if (vi)
+		fsverity_readahead(vi, readahead_index(rac),
+				   readahead_count(rac));
+	f2fs_mpage_readpages(inode, vi, rac, NULL);
 }
 
 int f2fs_encrypt_one_page(struct f2fs_io_info *fio)
@@ -3653,6 +3660,7 @@ static int f2fs_write_begin(const struct kiocb *iocb,
 		}
 		f2fs_submit_page_read(use_cow ?
 				F2FS_I(inode)->cow_inode : inode,
+				NULL, /* can't write to fsverity files */
 				folio, blkaddr, 0, true);
 
 		folio_lock(folio);
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index 20edbb99b814..f2fcadc7a6fe 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -1603,6 +1603,7 @@ struct compress_ctx {
 	size_t clen;			/* valid data length in cbuf */
 	void *private;			/* payload buffer for specified compression algorithm */
 	void *private2;			/* extra payload buffer */
+	struct fsverity_info *vi;	/* verity info if needed */
 };
 
 /* compress context for write IO path */
@@ -1658,7 +1659,7 @@ struct decompress_io_ctx {
 	refcount_t refcnt;
 
 	bool failed;			/* IO error occurred before decompression? */
-	bool need_verity;		/* need fs-verity verification after decompression? */
+	struct fsverity_info *vi;	/* fs-verity context if needed */
 	unsigned char compress_algorithm;	/* backup algorithm type */
 	void *private;			/* payload buffer for specified decompression algorithm */
 	void *private2;			/* extra payload buffer */
@@ -4886,12 +4887,6 @@ static inline bool f2fs_allow_multi_device_dio(struct f2fs_sb_info *sbi,
 	return sbi->aligned_blksize;
 }
 
-static inline bool f2fs_need_verity(const struct inode *inode, pgoff_t idx)
-{
-	return fsverity_active(inode) &&
-	       idx < DIV_ROUND_UP(inode->i_size, PAGE_SIZE);
-}
-
 #ifdef CONFIG_F2FS_FAULT_INJECTION
 extern int f2fs_build_fault_attr(struct f2fs_sb_info *sbi, unsigned long rate,
 					unsigned long type, enum fault_option fo);
-- 
2.47.3


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

* [PATCH 10/11] btrfs: consolidate fsverity_info lookup
  2026-02-02  6:06 fsverity speedup and memory usage optimization v5 Christoph Hellwig
                   ` (8 preceding siblings ...)
  2026-02-02  6:06 ` [PATCH 09/11] f2fs: " Christoph Hellwig
@ 2026-02-02  6:06 ` Christoph Hellwig
  2026-02-02  6:06 ` [PATCH 11/11] fsverity: use a hashtable to find the fsverity_info Christoph Hellwig
  2026-02-02 21:14 ` fsverity speedup and memory usage optimization v5 Eric Biggers
  11 siblings, 0 replies; 28+ messages in thread
From: Christoph Hellwig @ 2026-02-02  6:06 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	Matthew Wilcox, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity

Look up the fsverity_info once in btrfs_do_readpage, and then use it
for all operations performed there, and do the same in end_folio_read
for all folios processed there.  The latter is also changed to derive
the inode from the btrfs_bio - while bbio->inode is optional, it is
always set for buffered reads.

This amortizes the lookup better once it becomes less efficient.

Signed-off-by: Christoph Hellwig <hch@lst.de>
Acked-by: David Sterba <dsterba@suse.com>
---
 fs/btrfs/extent_io.c | 54 +++++++++++++++++++++++++++-----------------
 1 file changed, 33 insertions(+), 21 deletions(-)

diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index 21430b7d8f27..24988520521c 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -476,26 +476,25 @@ void extent_clear_unlock_delalloc(struct btrfs_inode *inode, u64 start, u64 end,
 				end, page_ops);
 }
 
-static bool btrfs_verify_folio(struct folio *folio, u64 start, u32 len)
+static bool btrfs_verify_folio(struct fsverity_info *vi, struct folio *folio,
+			       u64 start, u32 len)
 {
 	struct btrfs_fs_info *fs_info = folio_to_fs_info(folio);
 
-	if (!fsverity_active(folio->mapping->host) ||
-	    btrfs_folio_test_uptodate(fs_info, folio, start, len) ||
-	    start >= i_size_read(folio->mapping->host))
+	if (!vi || btrfs_folio_test_uptodate(fs_info, folio, start, len))
 		return true;
-	return fsverity_verify_folio(*fsverity_info_addr(folio->mapping->host),
-			folio);
+	return fsverity_verify_folio(vi, folio);
 }
 
-static void end_folio_read(struct folio *folio, bool uptodate, u64 start, u32 len)
+static void end_folio_read(struct fsverity_info *vi, struct folio *folio,
+			   bool uptodate, u64 start, u32 len)
 {
 	struct btrfs_fs_info *fs_info = folio_to_fs_info(folio);
 
 	ASSERT(folio_pos(folio) <= start &&
 	       start + len <= folio_next_pos(folio));
 
-	if (uptodate && btrfs_verify_folio(folio, start, len))
+	if (uptodate && btrfs_verify_folio(vi, folio, start, len))
 		btrfs_folio_set_uptodate(fs_info, folio, start, len);
 	else
 		btrfs_folio_clear_uptodate(fs_info, folio, start, len);
@@ -575,14 +574,19 @@ static void begin_folio_read(struct btrfs_fs_info *fs_info, struct folio *folio)
 static void end_bbio_data_read(struct btrfs_bio *bbio)
 {
 	struct btrfs_fs_info *fs_info = bbio->inode->root->fs_info;
+	struct inode *inode = &bbio->inode->vfs_inode;
 	struct bio *bio = &bbio->bio;
+	struct fsverity_info *vi = NULL;
 	struct folio_iter fi;
 
 	ASSERT(!bio_flagged(bio, BIO_CLONED));
+
+	if (bbio->file_offset < i_size_read(inode))
+		vi = fsverity_get_info(inode);
+
 	bio_for_each_folio_all(fi, &bbio->bio) {
 		bool uptodate = !bio->bi_status;
 		struct folio *folio = fi.folio;
-		struct inode *inode = folio->mapping->host;
 		u64 start = folio_pos(folio) + fi.offset;
 
 		btrfs_debug(fs_info,
@@ -617,7 +621,7 @@ static void end_bbio_data_read(struct btrfs_bio *bbio)
 		}
 
 		/* Update page status and unlock. */
-		end_folio_read(folio, uptodate, start, fi.length);
+		end_folio_read(vi, folio, uptodate, start, fi.length);
 	}
 	bio_put(bio);
 }
@@ -992,7 +996,8 @@ static void btrfs_readahead_expand(struct readahead_control *ractl,
  * return 0 on success, otherwise return error
  */
 static int btrfs_do_readpage(struct folio *folio, struct extent_map **em_cached,
-			     struct btrfs_bio_ctrl *bio_ctrl)
+			     struct btrfs_bio_ctrl *bio_ctrl,
+			     struct fsverity_info *vi)
 {
 	struct inode *inode = folio->mapping->host;
 	struct btrfs_fs_info *fs_info = inode_to_fs_info(inode);
@@ -1030,16 +1035,16 @@ static int btrfs_do_readpage(struct folio *folio, struct extent_map **em_cached,
 		ASSERT(IS_ALIGNED(cur, fs_info->sectorsize));
 		if (cur >= last_byte) {
 			folio_zero_range(folio, pg_offset, end - cur + 1);
-			end_folio_read(folio, true, cur, end - cur + 1);
+			end_folio_read(vi, folio, true, cur, end - cur + 1);
 			break;
 		}
 		if (btrfs_folio_test_uptodate(fs_info, folio, cur, blocksize)) {
-			end_folio_read(folio, true, cur, blocksize);
+			end_folio_read(vi, folio, true, cur, blocksize);
 			continue;
 		}
 		em = get_extent_map(BTRFS_I(inode), folio, cur, end - cur + 1, em_cached);
 		if (IS_ERR(em)) {
-			end_folio_read(folio, false, cur, end + 1 - cur);
+			end_folio_read(vi, folio, false, cur, end + 1 - cur);
 			return PTR_ERR(em);
 		}
 		extent_offset = cur - em->start;
@@ -1116,12 +1121,12 @@ static int btrfs_do_readpage(struct folio *folio, struct extent_map **em_cached,
 		/* we've found a hole, just zero and go on */
 		if (block_start == EXTENT_MAP_HOLE) {
 			folio_zero_range(folio, pg_offset, blocksize);
-			end_folio_read(folio, true, cur, blocksize);
+			end_folio_read(vi, folio, true, cur, blocksize);
 			continue;
 		}
 		/* the get_extent function already copied into the folio */
 		if (block_start == EXTENT_MAP_INLINE) {
-			end_folio_read(folio, true, cur, blocksize);
+			end_folio_read(vi, folio, true, cur, blocksize);
 			continue;
 		}
 
@@ -1318,7 +1323,8 @@ static void lock_extents_for_read(struct btrfs_inode *inode, u64 start, u64 end,
 
 int btrfs_read_folio(struct file *file, struct folio *folio)
 {
-	struct btrfs_inode *inode = folio_to_inode(folio);
+	struct inode *vfs_inode = folio->mapping->host;
+	struct btrfs_inode *inode = BTRFS_I(vfs_inode);
 	const u64 start = folio_pos(folio);
 	const u64 end = start + folio_size(folio) - 1;
 	struct extent_state *cached_state = NULL;
@@ -1327,10 +1333,13 @@ int btrfs_read_folio(struct file *file, struct folio *folio)
 		.last_em_start = U64_MAX,
 	};
 	struct extent_map *em_cached = NULL;
+	struct fsverity_info *vi = NULL;
 	int ret;
 
 	lock_extents_for_read(inode, start, end, &cached_state);
-	ret = btrfs_do_readpage(folio, &em_cached, &bio_ctrl);
+	if (folio_pos(folio) < i_size_read(vfs_inode))
+		vi = fsverity_get_info(vfs_inode);
+	ret = btrfs_do_readpage(folio, &em_cached, &bio_ctrl, vi);
 	btrfs_unlock_extent(&inode->io_tree, start, end, &cached_state);
 
 	btrfs_free_extent_map(em_cached);
@@ -2697,16 +2706,19 @@ void btrfs_readahead(struct readahead_control *rac)
 		.last_em_start = U64_MAX,
 	};
 	struct folio *folio;
-	struct btrfs_inode *inode = BTRFS_I(rac->mapping->host);
+	struct inode *vfs_inode = rac->mapping->host;
+	struct btrfs_inode *inode = BTRFS_I(vfs_inode);
 	const u64 start = readahead_pos(rac);
 	const u64 end = start + readahead_length(rac) - 1;
 	struct extent_state *cached_state = NULL;
 	struct extent_map *em_cached = NULL;
+	struct fsverity_info *vi = NULL;
 
 	lock_extents_for_read(inode, start, end, &cached_state);
-
+	if (start < i_size_read(vfs_inode))
+		vi = fsverity_get_info(vfs_inode);
 	while ((folio = readahead_folio(rac)) != NULL)
-		btrfs_do_readpage(folio, &em_cached, &bio_ctrl);
+		btrfs_do_readpage(folio, &em_cached, &bio_ctrl, vi);
 
 	btrfs_unlock_extent(&inode->io_tree, start, end, &cached_state);
 
-- 
2.47.3


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

* [PATCH 11/11] fsverity: use a hashtable to find the fsverity_info
  2026-02-02  6:06 fsverity speedup and memory usage optimization v5 Christoph Hellwig
                   ` (9 preceding siblings ...)
  2026-02-02  6:06 ` [PATCH 10/11] btrfs: " Christoph Hellwig
@ 2026-02-02  6:06 ` Christoph Hellwig
  2026-02-02 21:14 ` fsverity speedup and memory usage optimization v5 Eric Biggers
  11 siblings, 0 replies; 28+ messages in thread
From: Christoph Hellwig @ 2026-02-02  6:06 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	Matthew Wilcox, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity, Darrick J. Wong

Use the kernel's resizable hash table (rhashtable) 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
trade-off 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.

The rhashtable implementations requires no external synchronization, and
the _fast versions of the APIs provide the RCU critical sections required
by the implementation.  Because struct fsverity_info is only removed on
inode eviction and does not contain a reference count, there is no need
for an extended critical section to grab a reference or validate the
object state.  The file open path uses rhashtable_lookup_get_insert_fast,
which can either find an existing object for the hash key or insert a
new one in a single atomic operation, so that concurrent opens never
instatiate duplicate fsverity_info structure.  FS_IOC_ENABLE_VERITY must
already be synchronized by a combination of i_rwsem and file system flags
and uses rhashtable_lookup_insert_fast, which errors out on an existing
object for the hash key as an additional safety check.

Because insertion into the hash table now happens before S_VERITY is set,
fsverity just becomes a barrier and a flag check and doesn't have to look
up the fsverity_info at all, so there is only a single lookup per
->read_folio or ->readahead invocation.  For btrfs there is an additional
one for each bio completion, while for ext4 and f2fs the fsverity_info
is stored in the per-I/O context and reused for the completion workqueue.

Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
 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           |  2 +-
 include/linux/fsverity.h     | 90 ++++++++++++++----------------------
 14 files changed, 109 insertions(+), 131 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 c152bef71e8b..cd96fac4739f 100644
--- a/fs/btrfs/verity.c
+++ b/fs/btrfs/verity.c
@@ -795,8 +795,6 @@ static int btrfs_write_merkle_tree_block(struct file *file, 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 a8a448e20ef8..79c319fbf5f0 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 54ae4d4a176c..e3ab3ba8799b 100644
--- a/fs/ext4/verity.c
+++ b/fs/ext4/verity.c
@@ -380,8 +380,6 @@ static int ext4_write_merkle_tree_block(struct file *file, 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 f2fcadc7a6fe..8ee8a7bc012c 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 628e8eafa96a..4f5230d871f7 100644
--- a/fs/f2fs/verity.c
+++ b/fs/f2fs/verity.c
@@ -278,8 +278,6 @@ static int f2fs_write_merkle_tree_block(struct file *file, 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 c56c18e2605b..94c88c419054 100644
--- a/fs/verity/enable.c
+++ b/fs/verity/enable.c
@@ -265,9 +265,24 @@ static int enable_verity(struct file *filp,
 		goto rollback;
 	}
 
+	/*
+	 * Add the fsverity_info into the hash table before finishing the
+	 * initialization so that 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);
@@ -275,19 +290,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 f9f3936b0a89..4d4a0a560562 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(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 128502cf0a23..1bde8fe79b3f 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_field(struct fsverity_info, 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(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;
@@ -384,16 +394,25 @@ int __fsverity_file_open(struct inode *inode, struct file *filp)
 }
 EXPORT_SYMBOL_GPL(__fsverity_file_open);
 
+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 cf4c00273c16..7380d0c5123c 100644
--- a/fs/verity/verify.c
+++ b/fs/verity/verify.c
@@ -320,7 +320,7 @@ fsverity_init_verification_context(struct fsverity_verification_context *ctx,
 	ctx->inode = vi->inode;
 	ctx->vi = vi;
 	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 ab7244f7d172..501ba8c96084 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.
 	 *
@@ -142,38 +135,43 @@ struct fsverity_operations {
 };
 
 #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.)
+/**
+ * fsverity_active() - do reads from the inode need to go through fs-verity?
+ * @inode: inode to check
+ *
+ * This checks whether the inode's verity info has been set, and reads need
+ * to verify the file data.
+ *
+ * Return: true if reads need to go through fs-verity, otherwise false
  */
-static inline struct fsverity_info **
-fsverity_info_addr(const struct inode *inode)
+static inline bool fsverity_active(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;
+	if (IS_VERITY(inode)) {
+		/*
+		 * This pairs with the try_cmpxchg in set_mask_bits()
+		 * used to set the S_VERITY bit in i_flags.
+		 */
+		smp_mb();
+		return true;
+	}
+
+	return false;
 }
 
+/**
+ * fsverity_get_info - get fsverity information for an inode
+ * @inode: inode to operate on.
+ *
+ * This gets the fsverity_info for @inode if it exists.  Safe to call without
+ * knowin that a fsverity_info exist for @inode, including on file systems that
+ * do not support fsverity.
+ */
+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.
-	 */
-	if (!IS_VERITY(inode))
+	if (!fsverity_active(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);
 }
 
 /* enable.c */
@@ -204,12 +202,10 @@ void fsverity_enqueue_verify_work(struct work_struct *work);
 
 #else /* !CONFIG_FS_VERITY */
 
-/*
- * Provide a stub to allow code using this to compile.  All callsites should be
- * guarded by compiler dead code elimination, and this forces a link error if
- * not.
- */
-struct fsverity_info **fsverity_info_addr(const struct inode *inode);
+static inline bool fsverity_active(const struct inode *inode)
+{
+	return false;
+}
 
 static inline struct fsverity_info *fsverity_get_info(const struct inode *inode)
 {
@@ -292,24 +288,6 @@ static inline bool fsverity_verify_page(struct fsverity_info *vi,
 	return fsverity_verify_blocks(vi, page_folio(page), PAGE_SIZE, 0);
 }
 
-/**
- * fsverity_active() - do reads from the inode need to go through fs-verity?
- * @inode: inode to check
- *
- * This checks whether the inode's verity info has been set.
- *
- * Filesystems call this from ->readahead() to check whether the pages need to
- * be verified or not.  Don't use IS_VERITY() for this purpose; it's subject to
- * a race condition where the file is being read concurrently with
- * FS_IOC_ENABLE_VERITY completing.  (S_VERITY is set before the verity info.)
- *
- * Return: true if reads need to go through fs-verity, otherwise false
- */
-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
-- 
2.47.3


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

* Re: [PATCH 01/11] fsverity: don't issue readahead for non-ENOENT errors from __filemap_get_folio
  2026-02-02  6:06 ` [PATCH 01/11] fsverity: don't issue readahead for non-ENOENT errors from __filemap_get_folio Christoph Hellwig
@ 2026-02-02 13:23   ` Jan Kara
  2026-02-17 21:14   ` [f2fs-dev] " patchwork-bot+f2fs
  1 sibling, 0 replies; 28+ messages in thread
From: Jan Kara @ 2026-02-02 13:23 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,
	Matthew Wilcox, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity

On Mon 02-02-26 07:06:30, Christoph Hellwig wrote:
> Issuing more reads on errors is not a good idea, especially when the
> most common error here is -ENOMEM.
> 
> Signed-off-by: Christoph Hellwig <hch@lst.de>

Looks good. Feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> ---
>  fs/verity/pagecache.c | 7 ++++---
>  1 file changed, 4 insertions(+), 3 deletions(-)
> 
> diff --git a/fs/verity/pagecache.c b/fs/verity/pagecache.c
> index 01c652bc802f..1a88decace53 100644
> --- a/fs/verity/pagecache.c
> +++ b/fs/verity/pagecache.c
> @@ -22,7 +22,8 @@ struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index,
>  	struct folio *folio;
>  
>  	folio = __filemap_get_folio(inode->i_mapping, index, FGP_ACCESSED, 0);
> -	if (IS_ERR(folio) || !folio_test_uptodate(folio)) {
> +	if (folio == ERR_PTR(-ENOENT) ||
> +	    (!IS_ERR(folio) && !folio_test_uptodate(folio))) {
>  		DEFINE_READAHEAD(ractl, NULL, NULL, inode->i_mapping, index);
>  
>  		if (!IS_ERR(folio))
> @@ -30,9 +31,9 @@ struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index,
>  		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);
>  	}
> +	if (IS_ERR(folio))
> +		return ERR_CAST(folio);
>  	return folio_file_page(folio, index);
>  }
>  EXPORT_SYMBOL_GPL(generic_read_merkle_tree_page);
> -- 
> 2.47.3
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH 03/11] ext4: move ->read_folio and ->readahead to readahead.c
  2026-02-02  6:06 ` [PATCH 03/11] ext4: move ->read_folio and ->readahead to readahead.c Christoph Hellwig
@ 2026-02-02 13:32   ` Jan Kara
  2026-02-03  0:57   ` Theodore Tso
  1 sibling, 0 replies; 28+ messages in thread
From: Jan Kara @ 2026-02-02 13:32 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,
	Matthew Wilcox, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity

On Mon 02-02-26 07:06:32, Christoph Hellwig wrote:
> Keep all the read into pagecache code in a single file.
> 
> Signed-off-by: Christoph Hellwig <hch@lst.de>

Whatever :). Feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> ---
>  fs/ext4/ext4.h     |  4 ++--
>  fs/ext4/inode.c    | 27 ---------------------------
>  fs/ext4/readpage.c | 31 ++++++++++++++++++++++++++++++-
>  3 files changed, 32 insertions(+), 30 deletions(-)
> 
> diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
> index 56112f201cac..a8a448e20ef8 100644
> --- a/fs/ext4/ext4.h
> +++ b/fs/ext4/ext4.h
> @@ -3735,8 +3735,8 @@ static inline void ext4_set_de_type(struct super_block *sb,
>  }
>  
>  /* readpages.c */
> -extern int ext4_mpage_readpages(struct inode *inode,
> -		struct readahead_control *rac, struct folio *folio);
> +int ext4_read_folio(struct file *file, struct folio *folio);
> +void ext4_readahead(struct readahead_control *rac);
>  extern int __init ext4_init_post_read_processing(void);
>  extern void ext4_exit_post_read_processing(void);
>  
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index 8c2ef98fa530..e98954e7d0b3 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -3380,33 +3380,6 @@ static sector_t ext4_bmap(struct address_space *mapping, sector_t block)
>  	return ret;
>  }
>  
> -static int ext4_read_folio(struct file *file, struct folio *folio)
> -{
> -	int ret = -EAGAIN;
> -	struct inode *inode = folio->mapping->host;
> -
> -	trace_ext4_read_folio(inode, folio);
> -
> -	if (ext4_has_inline_data(inode))
> -		ret = ext4_readpage_inline(inode, folio);
> -
> -	if (ret == -EAGAIN)
> -		return ext4_mpage_readpages(inode, NULL, folio);
> -
> -	return ret;
> -}
> -
> -static void ext4_readahead(struct readahead_control *rac)
> -{
> -	struct inode *inode = rac->mapping->host;
> -
> -	/* If the file has inline data, no need to do readahead. */
> -	if (ext4_has_inline_data(inode))
> -		return;
> -
> -	ext4_mpage_readpages(inode, rac, NULL);
> -}
> -
>  static void ext4_invalidate_folio(struct folio *folio, size_t offset,
>  				size_t length)
>  {
> diff --git a/fs/ext4/readpage.c b/fs/ext4/readpage.c
> index 267594ef0b2c..bf84952ebf94 100644
> --- a/fs/ext4/readpage.c
> +++ b/fs/ext4/readpage.c
> @@ -45,6 +45,7 @@
>  #include <linux/pagevec.h>
>  
>  #include "ext4.h"
> +#include <trace/events/ext4.h>
>  
>  #define NUM_PREALLOC_POST_READ_CTXS	128
>  
> @@ -209,7 +210,7 @@ static inline loff_t ext4_readpage_limit(struct inode *inode)
>  	return i_size_read(inode);
>  }
>  
> -int ext4_mpage_readpages(struct inode *inode,
> +static int ext4_mpage_readpages(struct inode *inode,
>  		struct readahead_control *rac, struct folio *folio)
>  {
>  	struct bio *bio = NULL;
> @@ -394,6 +395,34 @@ int ext4_mpage_readpages(struct inode *inode,
>  	return 0;
>  }
>  
> +int ext4_read_folio(struct file *file, struct folio *folio)
> +{
> +	int ret = -EAGAIN;
> +	struct inode *inode = folio->mapping->host;
> +
> +	trace_ext4_read_folio(inode, folio);
> +
> +	if (ext4_has_inline_data(inode))
> +		ret = ext4_readpage_inline(inode, folio);
> +
> +	if (ret == -EAGAIN)
> +		return ext4_mpage_readpages(inode, NULL, folio);
> +
> +	return ret;
> +}
> +
> +void ext4_readahead(struct readahead_control *rac)
> +{
> +	struct inode *inode = rac->mapping->host;
> +
> +	/* If the file has inline data, no need to do readahead. */
> +	if (ext4_has_inline_data(inode))
> +		return;
> +
> +	ext4_mpage_readpages(inode, rac, NULL);
> +}
> +
> +
>  int __init ext4_init_post_read_processing(void)
>  {
>  	bio_post_read_ctx_cache = KMEM_CACHE(bio_post_read_ctx, SLAB_RECLAIM_ACCOUNT);
> -- 
> 2.47.3
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH 02/11] readahead: push invalidate_lock out of page_cache_ra_unbounded
  2026-02-02  6:06 ` [PATCH 02/11] readahead: push invalidate_lock out of page_cache_ra_unbounded Christoph Hellwig
@ 2026-02-02 13:36   ` Jan Kara
  2026-02-02 15:11   ` Matthew Wilcox
  1 sibling, 0 replies; 28+ messages in thread
From: Jan Kara @ 2026-02-02 13:36 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,
	Matthew Wilcox, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity

On Mon 02-02-26 07:06:31, Christoph Hellwig wrote:
> Require the invalidate_lock to be held over calls to
> page_cache_ra_unbounded instead of acquiring it in this function.
> 
> This prepares for calling page_cache_ra_unbounded from ->readahead for
> fsverity read-ahead.
> 
> Signed-off-by: Christoph Hellwig <hch@lst.de>

Looks good to me. Feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
> index da029fed4e5a..c9b9fcdd0cae 100644
> --- a/fs/f2fs/file.c
> +++ b/fs/f2fs/file.c
> @@ -4418,7 +4418,9 @@ static int redirty_blocks(struct inode *inode, pgoff_t page_idx, int len)
>  	pgoff_t redirty_idx = page_idx;
>  	int page_len = 0, ret = 0;
>  
> +	filemap_invalidate_lock_shared(mapping);
>  	page_cache_ra_unbounded(&ractl, len, 0);
> +	filemap_invalidate_unlock_shared(mapping);
>  
>  	do {
>  		folio = read_cache_folio(mapping, page_idx, NULL, NULL);
> diff --git a/fs/verity/pagecache.c b/fs/verity/pagecache.c
> index 1a88decace53..8e0d6fde802f 100644
> --- a/fs/verity/pagecache.c
> +++ b/fs/verity/pagecache.c
> @@ -26,10 +26,13 @@ struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index,
>  	    (!IS_ERR(folio) && !folio_test_uptodate(folio))) {
>  		DEFINE_READAHEAD(ractl, NULL, NULL, inode->i_mapping, index);
>  
> -		if (!IS_ERR(folio))
> +		if (!IS_ERR(folio)) {
>  			folio_put(folio);
> -		else if (num_ra_pages > 1)
> +		} else if (num_ra_pages > 1) {
> +			filemap_invalidate_lock_shared(inode->i_mapping);
>  			page_cache_ra_unbounded(&ractl, num_ra_pages, 0);
> +			filemap_invalidate_unlock_shared(inode->i_mapping);
> +		}
>  		folio = read_mapping_folio(inode->i_mapping, index, NULL);
>  	}
>  	if (IS_ERR(folio))
> diff --git a/mm/readahead.c b/mm/readahead.c
> index b415c9969176..25f81124beb6 100644
> --- a/mm/readahead.c
> +++ b/mm/readahead.c
> @@ -204,7 +204,8 @@ static struct folio *ractl_alloc_folio(struct readahead_control *ractl,
>   * not the function you want to call.  Use page_cache_async_readahead()
>   * or page_cache_sync_readahead() instead.
>   *
> - * Context: File is referenced by caller.  Mutexes may be held by caller.
> + * Context: File is referenced by caller, and ractl->mapping->invalidate_lock
> + * must be held by the caller in shared mode.  Mutexes may be held by caller.
>   * May sleep, but will not reenter filesystem to reclaim memory.
>   */
>  void page_cache_ra_unbounded(struct readahead_control *ractl,
> @@ -228,9 +229,10 @@ void page_cache_ra_unbounded(struct readahead_control *ractl,
>  	 */
>  	unsigned int nofs = memalloc_nofs_save();
>  
> +	lockdep_assert_held_read(&mapping->invalidate_lock);
> +
>  	trace_page_cache_ra_unbounded(mapping->host, index, nr_to_read,
>  				      lookahead_size);
> -	filemap_invalidate_lock_shared(mapping);
>  	index = mapping_align_index(mapping, index);
>  
>  	/*
> @@ -300,7 +302,6 @@ void page_cache_ra_unbounded(struct readahead_control *ractl,
>  	 * will then handle the error.
>  	 */
>  	read_pages(ractl);
> -	filemap_invalidate_unlock_shared(mapping);
>  	memalloc_nofs_restore(nofs);
>  }
>  EXPORT_SYMBOL_GPL(page_cache_ra_unbounded);
> @@ -314,9 +315,9 @@ EXPORT_SYMBOL_GPL(page_cache_ra_unbounded);
>  static void do_page_cache_ra(struct readahead_control *ractl,
>  		unsigned long nr_to_read, unsigned long lookahead_size)
>  {
> -	struct inode *inode = ractl->mapping->host;
> +	struct address_space *mapping = ractl->mapping;
>  	unsigned long index = readahead_index(ractl);
> -	loff_t isize = i_size_read(inode);
> +	loff_t isize = i_size_read(mapping->host);
>  	pgoff_t end_index;	/* The last page we want to read */
>  
>  	if (isize == 0)
> @@ -329,7 +330,9 @@ static void do_page_cache_ra(struct readahead_control *ractl,
>  	if (nr_to_read > end_index - index)
>  		nr_to_read = end_index - index + 1;
>  
> +	filemap_invalidate_lock_shared(mapping);
>  	page_cache_ra_unbounded(ractl, nr_to_read, lookahead_size);
> +	filemap_invalidate_unlock_shared(mapping);
>  }
>  
>  /*
> -- 
> 2.47.3
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH 07/11] fs: consolidate fsverity_info lookup in buffer.c
  2026-02-02  6:06 ` [PATCH 07/11] fs: consolidate fsverity_info lookup in buffer.c Christoph Hellwig
@ 2026-02-02 13:38   ` Jan Kara
  0 siblings, 0 replies; 28+ messages in thread
From: Jan Kara @ 2026-02-02 13:38 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,
	Matthew Wilcox, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity, Darrick J. Wong

On Mon 02-02-26 07:06:36, Christoph Hellwig wrote:
> Look up the fsverity_info once in end_buffer_async_read_io, and then
> pass it along to the I/O completion workqueue in
> struct postprocess_bh_ctx.
> 
> This amortizes the lookup better once it becomes less efficient.
> 
> Signed-off-by: Christoph Hellwig <hch@lst.de>
> Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>

Looks good. Feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> ---
>  fs/buffer.c | 27 +++++++++++----------------
>  1 file changed, 11 insertions(+), 16 deletions(-)
> 
> diff --git a/fs/buffer.c b/fs/buffer.c
> index 3982253b6805..f4b3297ef1b1 100644
> --- a/fs/buffer.c
> +++ b/fs/buffer.c
> @@ -302,6 +302,7 @@ static void end_buffer_async_read(struct buffer_head *bh, int uptodate)
>  struct postprocess_bh_ctx {
>  	struct work_struct work;
>  	struct buffer_head *bh;
> +	struct fsverity_info *vi;
>  };
>  
>  static void verify_bh(struct work_struct *work)
> @@ -309,25 +310,14 @@ static void verify_bh(struct work_struct *work)
>  	struct postprocess_bh_ctx *ctx =
>  		container_of(work, struct postprocess_bh_ctx, work);
>  	struct buffer_head *bh = ctx->bh;
> -	struct inode *inode = bh->b_folio->mapping->host;
>  	bool valid;
>  
> -	valid = fsverity_verify_blocks(*fsverity_info_addr(inode), bh->b_folio,
> -				       bh->b_size, bh_offset(bh));
> +	valid = fsverity_verify_blocks(ctx->vi, bh->b_folio, bh->b_size,
> +				       bh_offset(bh));
>  	end_buffer_async_read(bh, valid);
>  	kfree(ctx);
>  }
>  
> -static bool need_fsverity(struct buffer_head *bh)
> -{
> -	struct folio *folio = bh->b_folio;
> -	struct inode *inode = folio->mapping->host;
> -
> -	return fsverity_active(inode) &&
> -		/* needed by ext4 */
> -		folio->index < DIV_ROUND_UP(inode->i_size, PAGE_SIZE);
> -}
> -
>  static void decrypt_bh(struct work_struct *work)
>  {
>  	struct postprocess_bh_ctx *ctx =
> @@ -337,7 +327,7 @@ static void decrypt_bh(struct work_struct *work)
>  
>  	err = fscrypt_decrypt_pagecache_blocks(bh->b_folio, bh->b_size,
>  					       bh_offset(bh));
> -	if (err == 0 && need_fsverity(bh)) {
> +	if (err == 0 && ctx->vi) {
>  		/*
>  		 * We use different work queues for decryption and for verity
>  		 * because verity may require reading metadata pages that need
> @@ -359,15 +349,20 @@ static void end_buffer_async_read_io(struct buffer_head *bh, int uptodate)
>  {
>  	struct inode *inode = bh->b_folio->mapping->host;
>  	bool decrypt = fscrypt_inode_uses_fs_layer_crypto(inode);
> -	bool verify = need_fsverity(bh);
> +	struct fsverity_info *vi = NULL;
> +
> +	/* needed by ext4 */
> +	if (bh->b_folio->index < DIV_ROUND_UP(inode->i_size, PAGE_SIZE))
> +		vi = fsverity_get_info(inode);
>  
>  	/* Decrypt (with fscrypt) and/or verify (with fsverity) if needed. */
> -	if (uptodate && (decrypt || verify)) {
> +	if (uptodate && (decrypt || vi)) {
>  		struct postprocess_bh_ctx *ctx =
>  			kmalloc(sizeof(*ctx), GFP_ATOMIC);
>  
>  		if (ctx) {
>  			ctx->bh = bh;
> +			ctx->vi = vi;
>  			if (decrypt) {
>  				INIT_WORK(&ctx->work, decrypt_bh);
>  				fscrypt_enqueue_decrypt_work(&ctx->work);
> -- 
> 2.47.3
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH 02/11] readahead: push invalidate_lock out of page_cache_ra_unbounded
  2026-02-02  6:06 ` [PATCH 02/11] readahead: push invalidate_lock out of page_cache_ra_unbounded Christoph Hellwig
  2026-02-02 13:36   ` Jan Kara
@ 2026-02-02 15:11   ` Matthew Wilcox
  2026-02-02 15:17     ` Christoph Hellwig
  1 sibling, 1 reply; 28+ messages in thread
From: Matthew Wilcox @ 2026-02-02 15:11 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, Feb 02, 2026 at 07:06:31AM +0100, Christoph Hellwig wrote:
> +++ b/fs/f2fs/file.c
> @@ -4418,7 +4418,9 @@ static int redirty_blocks(struct inode *inode, pgoff_t page_idx, int len)
>  	pgoff_t redirty_idx = page_idx;
>  	int page_len = 0, ret = 0;
>  
> +	filemap_invalidate_lock_shared(mapping);
>  	page_cache_ra_unbounded(&ractl, len, 0);
> +	filemap_invalidate_unlock_shared(mapping);

Why is f2fs calling page_cache_ra_unbounded() here?  The documentation
literally says not to call it:

 * This function is for filesystems to call when they want to start
 * readahead beyond a file's stated i_size.  This is almost certainly
 * not the function you want to call.  Use page_cache_async_readahead()
 * or page_cache_sync_readahead() instead.

(in this case, f2fs doesn't have a folio, so page_cache_async_ra() is
probably the right function to call).  But what's the point in writing
documentation when people don't read it?

> @@ -228,9 +229,10 @@ void page_cache_ra_unbounded(struct readahead_control *ractl,
>  	 */
>  	unsigned int nofs = memalloc_nofs_save();
>  
> +	lockdep_assert_held_read(&mapping->invalidate_lock);

Hm, why are we asserting that it's not write-locked?  For the
purposes of this function, I'd think we want to just
lockdep_assert_held()?

In the tree I'm looking at, there are also calls to
page_cache_ra_unbounded() in fs/ext4/verity.c and fs/f2fs/verity.c
which probably need the lock taken too?

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

* Re: [PATCH 02/11] readahead: push invalidate_lock out of page_cache_ra_unbounded
  2026-02-02 15:11   ` Matthew Wilcox
@ 2026-02-02 15:17     ` Christoph Hellwig
  2026-02-02 21:04       ` Eric Biggers
  0 siblings, 1 reply; 28+ messages in thread
From: Christoph Hellwig @ 2026-02-02 15:17 UTC (permalink / raw)
  To: Matthew Wilcox
  Cc: Christoph Hellwig, 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, Feb 02, 2026 at 03:11:45PM +0000, Matthew Wilcox wrote:
> On Mon, Feb 02, 2026 at 07:06:31AM +0100, Christoph Hellwig wrote:
> > +++ b/fs/f2fs/file.c
> > @@ -4418,7 +4418,9 @@ static int redirty_blocks(struct inode *inode, pgoff_t page_idx, int len)
> >  	pgoff_t redirty_idx = page_idx;
> >  	int page_len = 0, ret = 0;
> >  
> > +	filemap_invalidate_lock_shared(mapping);
> >  	page_cache_ra_unbounded(&ractl, len, 0);
> > +	filemap_invalidate_unlock_shared(mapping);
> 
> Why is f2fs calling page_cache_ra_unbounded() here?

From tracing the callers is seems to be able to be called from the
garbage collector, which might have to move fsverity files.  Not sure if
that was the reason or is incidental.

(using the pagecache for GC is generally a very bad idea, and there is
at least one academic paper showing it is a huge performance problem in
f2fs, and my initial attempts at using the pagecache for GC in zoned XFS
also showed horrible results)

> >  	unsigned int nofs = memalloc_nofs_save();
> >  
> > +	lockdep_assert_held_read(&mapping->invalidate_lock);
> 
> Hm, why are we asserting that it's not write-locked?  For the
> purposes of this function, I'd think we want to just
> lockdep_assert_held()?

Fine with me.

> In the tree I'm looking at, there are also calls to
> page_cache_ra_unbounded() in fs/ext4/verity.c and fs/f2fs/verity.c
> which probably need the lock taken too?

I consolidated those into the single call in fs/verity/pagecache.c
in the previous iteration of this series, and Eric merged the
first few patches including that one into the fsverity tree.

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

* Re: [PATCH 02/11] readahead: push invalidate_lock out of page_cache_ra_unbounded
  2026-02-02 15:17     ` Christoph Hellwig
@ 2026-02-02 21:04       ` Eric Biggers
  0 siblings, 0 replies; 28+ messages in thread
From: Eric Biggers @ 2026-02-02 21:04 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Matthew Wilcox, 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, Feb 02, 2026 at 04:17:55PM +0100, Christoph Hellwig wrote:
> On Mon, Feb 02, 2026 at 03:11:45PM +0000, Matthew Wilcox wrote:
> > On Mon, Feb 02, 2026 at 07:06:31AM +0100, Christoph Hellwig wrote:
> > > +++ b/fs/f2fs/file.c
> > > @@ -4418,7 +4418,9 @@ static int redirty_blocks(struct inode *inode, pgoff_t page_idx, int len)
> > >  	pgoff_t redirty_idx = page_idx;
> > >  	int page_len = 0, ret = 0;
> > >  
> > > +	filemap_invalidate_lock_shared(mapping);
> > >  	page_cache_ra_unbounded(&ractl, len, 0);
> > > +	filemap_invalidate_unlock_shared(mapping);
> > 
> > Why is f2fs calling page_cache_ra_unbounded() here?
> 
> From tracing the callers is seems to be able to be called from the
> garbage collector, which might have to move fsverity files.  Not sure if
> that was the reason or is incidental.
> 
> (using the pagecache for GC is generally a very bad idea, and there is
> at least one academic paper showing it is a huge performance problem in
> f2fs, and my initial attempts at using the pagecache for GC in zoned XFS
> also showed horrible results)
> 
> > >  	unsigned int nofs = memalloc_nofs_save();
> > >  
> > > +	lockdep_assert_held_read(&mapping->invalidate_lock);
> > 
> > Hm, why are we asserting that it's not write-locked?  For the
> > purposes of this function, I'd think we want to just
> > lockdep_assert_held()?
> 
> Fine with me.
> 
> > In the tree I'm looking at, there are also calls to
> > page_cache_ra_unbounded() in fs/ext4/verity.c and fs/f2fs/verity.c
> > which probably need the lock taken too?
> 
> I consolidated those into the single call in fs/verity/pagecache.c
> in the previous iteration of this series, and Eric merged the
> first few patches including that one into the fsverity tree.

I changed both instances of lockdep_assert_held_read() to
lockdep_assert_held() when applying.

- Eric

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

* Re: fsverity speedup and memory usage optimization v5
  2026-02-02  6:06 fsverity speedup and memory usage optimization v5 Christoph Hellwig
                   ` (10 preceding siblings ...)
  2026-02-02  6:06 ` [PATCH 11/11] fsverity: use a hashtable to find the fsverity_info Christoph Hellwig
@ 2026-02-02 21:14 ` Eric Biggers
  2026-02-02 22:34   ` Eric Biggers
  2026-02-04 14:54   ` Matthew Wilcox
  11 siblings, 2 replies; 28+ messages in thread
From: Eric Biggers @ 2026-02-02 21:14 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	Matthew Wilcox, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity

On Mon, Feb 02, 2026 at 07:06:29AM +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 part optimizes the fsverity 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.
> 
> The second part avoids the need for a pointer in every inode for fsverity
> and instead uses a rhashtable lookup, which is done once per read_folio
> or ->readahead invocation plus for btrfs only for each bio completion.
> Right now this does not increse the number of inodes in
> each slab, but for ext4 we are getting very close to that (within
> 16 bytes by my count).
> 
> Changes since v5:
>  - drop already merged patches
>  - fix a bisection hazard for non-ENOENT error returns from
>    generic_read_merkle_tree_page
>  - don't recurse on invalidate_lock
>  - refactor page_cache_ra_unbounded locking to support the above
>  - refactor ext4 and f2fs fsverity readahead to remove the need for the
>    first_folio branch in the main readpages loop

Applied to https://git.kernel.org/pub/scm/fs/fsverity/linux.git/log/?h=for-next

(Though it's getting late for v6.20 / v7.0.  So if there are any
additional issues reported, I may have to drop it.)

I fixed up some minor issues when applying:

- Replaced lockdep_assert_held_read() with lockdep_assert_held(), as
  mentioned in the other thread
- Fixed typos and other small mistakes in comments and commit messages
- Used the code formatting from 'git clang-format' in the cases where it
  looks better than the ad-hoc formatting
- Added <linux/export.h> to pagecache.c since it exports symbols, and
  dropped the unnecessary addition of <linux/pagemap.h> from verify.c

Here's the diff from the patchset as sent:

diff --git a/fs/ext4/readpage.c b/fs/ext4/readpage.c
index 09acca898c25e..f65f95cd75223 100644
--- a/fs/ext4/readpage.c
+++ b/fs/ext4/readpage.c
@@ -208,7 +208,8 @@ static inline loff_t ext4_readpage_limit(struct inode *inode)
 }
 
 static int ext4_mpage_readpages(struct inode *inode, struct fsverity_info *vi,
-		struct readahead_control *rac, struct folio *folio)
+				struct readahead_control *rac,
+				struct folio *folio)
 {
 	struct bio *bio = NULL;
 	sector_t last_block_in_bio = 0;
@@ -429,7 +430,6 @@ void ext4_readahead(struct readahead_control *rac)
 	ext4_mpage_readpages(inode, vi, rac, NULL);
 }
 
-
 int __init ext4_init_post_read_processing(void)
 {
 	bio_post_read_ctx_cache = KMEM_CACHE(bio_post_read_ctx, SLAB_RECLAIM_ACCOUNT);
diff --git a/fs/ext4/verity.c b/fs/ext4/verity.c
index e3ab3ba8799b2..5caa658adc12d 100644
--- a/fs/ext4/verity.c
+++ b/fs/ext4/verity.c
@@ -365,7 +365,7 @@ static struct page *ext4_read_merkle_tree_page(struct inode *inode,
 }
 
 static void ext4_readahead_merkle_tree(struct inode *inode, pgoff_t index,
-		unsigned long nr_pages)
+				       unsigned long nr_pages)
 {
 	index += ext4_verity_metadata_pos(inode) >> PAGE_SHIFT;
 	generic_readahead_merkle_tree(inode, index, nr_pages);
diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index 41a5f2d212828..bec7b6694876e 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -1039,8 +1039,9 @@ void f2fs_submit_page_write(struct f2fs_io_info *fio)
 }
 
 static struct bio *f2fs_grab_read_bio(struct inode *inode,
-		struct fsverity_info *vi, block_t blkaddr, unsigned nr_pages,
-		blk_opf_t op_flag, pgoff_t first_idx, bool for_write)
+				      struct fsverity_info *vi, block_t blkaddr,
+				      unsigned nr_pages, blk_opf_t op_flag,
+				      pgoff_t first_idx, bool for_write)
 {
 	struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
 	struct bio *bio;
@@ -1087,14 +1088,14 @@ static struct bio *f2fs_grab_read_bio(struct inode *inode,
 
 /* This can handle encryption stuffs */
 static void f2fs_submit_page_read(struct inode *inode, struct fsverity_info *vi,
-		struct folio *folio, block_t blkaddr, blk_opf_t op_flags,
-		bool for_write)
+				  struct folio *folio, block_t blkaddr,
+				  blk_opf_t op_flags, bool for_write)
 {
 	struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
 	struct bio *bio;
 
 	bio = f2fs_grab_read_bio(inode, vi, blkaddr, 1, op_flags, folio->index,
-			for_write);
+				 for_write);
 
 	/* wait for GCed page writeback via META_MAPPING */
 	f2fs_wait_on_block_writeback(inode, blkaddr);
@@ -1197,7 +1198,7 @@ int f2fs_reserve_block(struct dnode_of_data *dn, pgoff_t index)
 }
 
 static inline struct fsverity_info *f2fs_need_verity(const struct inode *inode,
-		pgoff_t idx)
+						     pgoff_t idx)
 {
 	if (idx < DIV_ROUND_UP(inode->i_size, PAGE_SIZE))
 		return fsverity_get_info(inode);
@@ -1270,7 +1271,7 @@ struct folio *f2fs_get_read_data_folio(struct inode *inode, pgoff_t index,
 	}
 
 	f2fs_submit_page_read(inode, f2fs_need_verity(inode, folio->index),
-			folio, dn.data_blkaddr, op_flags, for_write);
+			      folio, dn.data_blkaddr, op_flags, for_write);
 	return folio;
 
 put_err:
@@ -2075,9 +2076,11 @@ static inline blk_opf_t f2fs_ra_op_flags(struct readahead_control *rac)
 }
 
 static int f2fs_read_single_page(struct inode *inode, struct fsverity_info *vi,
-		struct folio *folio, unsigned nr_pages,
-		struct f2fs_map_blocks *map, struct bio **bio_ret,
-		sector_t *last_block_in_bio, struct readahead_control *rac)
+				 struct folio *folio, unsigned int nr_pages,
+				 struct f2fs_map_blocks *map,
+				 struct bio **bio_ret,
+				 sector_t *last_block_in_bio,
+				 struct readahead_control *rac)
 {
 	struct bio *bio = *bio_ret;
 	const unsigned int blocksize = F2FS_BLKSIZE;
@@ -2152,8 +2155,7 @@ static int f2fs_read_single_page(struct inode *inode, struct fsverity_info *vi,
 	}
 	if (bio == NULL)
 		bio = f2fs_grab_read_bio(inode, vi, block_nr, nr_pages,
-				f2fs_ra_op_flags(rac), index,
-				false);
+					 f2fs_ra_op_flags(rac), index, false);
 
 	/*
 	 * If the page is under writeback, we need to wait for
@@ -2304,8 +2306,9 @@ int f2fs_read_multi_pages(struct compress_ctx *cc, struct bio **bio_ret,
 
 		if (!bio)
 			bio = f2fs_grab_read_bio(inode, cc->vi, blkaddr,
-					nr_pages - i, f2fs_ra_op_flags(rac),
-					folio->index, for_write);
+						 nr_pages - i,
+						 f2fs_ra_op_flags(rac),
+						 folio->index, for_write);
 
 		if (!bio_add_folio(bio, folio, blocksize, 0))
 			goto submit_and_realloc;
@@ -2345,7 +2348,8 @@ int f2fs_read_multi_pages(struct compress_ctx *cc, struct bio **bio_ret,
  * Major change was from block_size == page_size in f2fs by default.
  */
 static int f2fs_mpage_readpages(struct inode *inode, struct fsverity_info *vi,
-		struct readahead_control *rac, struct folio *folio)
+				struct readahead_control *rac,
+				struct folio *folio)
 {
 	struct bio *bio = NULL;
 	sector_t last_block_in_bio = 0;
@@ -2434,7 +2438,8 @@ static int f2fs_mpage_readpages(struct inode *inode, struct fsverity_info *vi,
 #endif
 
 		ret = f2fs_read_single_page(inode, vi, folio, max_nr_pages,
-					&map, &bio, &last_block_in_bio, rac);
+					    &map, &bio, &last_block_in_bio,
+					    rac);
 		if (ret) {
 #ifdef CONFIG_F2FS_FS_COMPRESSION
 set_error_page:
@@ -3658,10 +3663,10 @@ static int f2fs_write_begin(const struct kiocb *iocb,
 			err = -EFSCORRUPTED;
 			goto put_folio;
 		}
-		f2fs_submit_page_read(use_cow ?
-				F2FS_I(inode)->cow_inode : inode,
-				NULL, /* can't write to fsverity files */
-				folio, blkaddr, 0, true);
+		f2fs_submit_page_read(use_cow ? F2FS_I(inode)->cow_inode :
+						inode,
+				      NULL, /* can't write to fsverity files */
+				      folio, blkaddr, 0, true);
 
 		folio_lock(folio);
 		if (unlikely(folio->mapping != mapping)) {
diff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c
index 4f5230d871f73..92ebcc19cab09 100644
--- a/fs/f2fs/verity.c
+++ b/fs/f2fs/verity.c
@@ -263,7 +263,7 @@ static struct page *f2fs_read_merkle_tree_page(struct inode *inode,
 }
 
 static void f2fs_readahead_merkle_tree(struct inode *inode, pgoff_t index,
-		unsigned long nr_pages)
+				       unsigned long nr_pages)
 {
 	index += f2fs_verity_metadata_pos(inode) >> PAGE_SHIFT;
 	generic_readahead_merkle_tree(inode, index, nr_pages);
diff --git a/fs/verity/open.c b/fs/verity/open.c
index 1bde8fe79b3f0..04b2e05a95d73 100644
--- a/fs/verity/open.c
+++ b/fs/verity/open.c
@@ -259,13 +259,14 @@ struct fsverity_info *fsverity_create_info(struct inode *inode,
 int fsverity_set_info(struct fsverity_info *vi)
 {
 	return rhashtable_lookup_insert_fast(&fsverity_info_hash,
-			&vi->rhash_head, fsverity_info_hash_params);
+					     &vi->rhash_head,
+					     fsverity_info_hash_params);
 }
 
 struct fsverity_info *__fsverity_get_info(const struct inode *inode)
 {
 	return rhashtable_lookup_fast(&fsverity_info_hash, &inode,
-			fsverity_info_hash_params);
+				      fsverity_info_hash_params);
 }
 EXPORT_SYMBOL_GPL(__fsverity_get_info);
 
@@ -374,7 +375,8 @@ static int ensure_verity_info(struct inode *inode)
 	 * 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);
+						  &vi->rhash_head,
+						  fsverity_info_hash_params);
 	if (found) {
 		fsverity_free_info(vi);
 		if (IS_ERR(found))
@@ -397,7 +399,7 @@ EXPORT_SYMBOL_GPL(__fsverity_file_open);
 void fsverity_remove_info(struct fsverity_info *vi)
 {
 	rhashtable_remove_fast(&fsverity_info_hash, &vi->rhash_head,
-			fsverity_info_hash_params);
+			       fsverity_info_hash_params);
 	fsverity_free_info(vi);
 }
 
diff --git a/fs/verity/pagecache.c b/fs/verity/pagecache.c
index 50b517ed6be0a..1819314ecaa35 100644
--- a/fs/verity/pagecache.c
+++ b/fs/verity/pagecache.c
@@ -3,6 +3,7 @@
  * Copyright 2019 Google LLC
  */
 
+#include <linux/export.h>
 #include <linux/fsverity.h>
 #include <linux/pagemap.h>
 
@@ -10,7 +11,6 @@
  * generic_read_merkle_tree_page - generic ->read_merkle_tree_page helper
  * @inode:	inode containing the Merkle tree
  * @index:	0-based index of the Merkle tree page in the inode
- * @num_ra_pages: The number of Merkle tree pages that should be prefetched.
  *
  * The caller needs to adjust @index from the Merkle-tree relative index passed
  * to ->read_merkle_tree_page to the actual index where the Merkle tree is
@@ -43,7 +43,7 @@ void generic_readahead_merkle_tree(struct inode *inode, pgoff_t index,
 {
 	struct folio *folio;
 
-	lockdep_assert_held_read(&inode->i_mapping->invalidate_lock);
+	lockdep_assert_held(&inode->i_mapping->invalidate_lock);
 
 	folio = __filemap_get_folio(inode->i_mapping, index, FGP_ACCESSED, 0);
 	if (folio == ERR_PTR(-ENOENT) ||
diff --git a/fs/verity/read_metadata.c b/fs/verity/read_metadata.c
index 2807d44dc6bbd..b4c0892430cde 100644
--- a/fs/verity/read_metadata.c
+++ b/fs/verity/read_metadata.c
@@ -37,8 +37,8 @@ static int fsverity_read_merkle_tree(struct inode *inode,
 	 */
 	if (inode->i_sb->s_vop->readahead_merkle_tree) {
 		filemap_invalidate_lock_shared(inode->i_mapping);
-		inode->i_sb->s_vop->readahead_merkle_tree(inode, index,
-				last_index - index + 1);
+		inode->i_sb->s_vop->readahead_merkle_tree(
+			inode, index, last_index - index + 1);
 		filemap_invalidate_unlock_shared(inode->i_mapping);
 	}
 
diff --git a/fs/verity/verify.c b/fs/verity/verify.c
index 7380d0c5123cb..37e000f01c180 100644
--- a/fs/verity/verify.c
+++ b/fs/verity/verify.c
@@ -9,7 +9,6 @@
 
 #include <linux/bio.h>
 #include <linux/export.h>
-#include <linux/pagemap.h>
 
 #define FS_VERITY_MAX_PENDING_BLOCKS 2
 
@@ -43,8 +42,8 @@ static struct workqueue_struct *fsverity_read_workqueue;
  * @index:		first file data page index that is being read
  * @nr_pages:		number of file data pages to be read
  *
- * Start readahead on the fsverity hashes that are needed to verity the file
- * data in the range from @index to @inode + @nr_pages.
+ * Start readahead on the fsverity hashes that are needed to verify the file
+ * data in the range from @index to @index + @nr_pages (exclusive upper bound).
  *
  * 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
@@ -68,12 +67,12 @@ void fsverity_readahead(struct fsverity_info *vi, pgoff_t index,
 		unsigned long next_start_hidx = start_hidx >> params->log_arity;
 		unsigned long next_end_hidx = end_hidx >> params->log_arity;
 		pgoff_t start_idx = (level_start + next_start_hidx) >>
-				params->log_blocks_per_page;
+				    params->log_blocks_per_page;
 		pgoff_t end_idx = (level_start + next_end_hidx) >>
-				params->log_blocks_per_page;
+				  params->log_blocks_per_page;
 
-		inode->i_sb->s_vop->readahead_merkle_tree(inode, start_idx,
-				end_idx - start_idx + 1);
+		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;
@@ -244,7 +243,7 @@ static bool verify_data_block(struct fsverity_info *vi,
 			  (params->block_size - 1);
 
 		hpage = inode->i_sb->s_vop->read_merkle_tree_page(inode,
-				hpage_idx);
+								  hpage_idx);
 		if (IS_ERR(hpage)) {
 			fsverity_err(inode,
 				     "Error %ld reading Merkle tree page %lu",
@@ -320,7 +319,7 @@ fsverity_init_verification_context(struct fsverity_verification_context *ctx,
 	ctx->inode = vi->inode;
 	ctx->vi = vi;
 	ctx->num_pending = 0;
-	if (ctx->vi->tree_params.hash_alg->algo_id == HASH_ALGO_SHA256 &&
+	if (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 501ba8c960844..fed91023bea9c 100644
--- a/include/linux/fsverity.h
+++ b/include/linux/fsverity.h
@@ -115,7 +115,7 @@ struct fsverity_operations {
 	 * cached data instead of issuing dependent I/O.
 	 */
 	void (*readahead_merkle_tree)(struct inode *inode, pgoff_t index,
-			unsigned long nr_pages);
+				      unsigned long nr_pages);
 
 	/**
 	 * Write a Merkle tree block to the given file.
@@ -158,6 +158,7 @@ static inline bool fsverity_active(const struct inode *inode)
 	return false;
 }
 
+struct fsverity_info *__fsverity_get_info(const struct inode *inode);
 /**
  * fsverity_get_info - get fsverity information for an inode
  * @inode: inode to operate on.
@@ -166,7 +167,6 @@ static inline bool fsverity_active(const struct inode *inode)
  * knowin that a fsverity_info exist for @inode, including on file systems that
  * do not support fsverity.
  */
-struct fsverity_info *__fsverity_get_info(const struct inode *inode);
 static inline struct fsverity_info *fsverity_get_info(const struct inode *inode)
 {
 	if (!fsverity_active(inode))
@@ -310,7 +310,7 @@ static inline int fsverity_file_open(struct inode *inode, struct file *filp)
 
 void fsverity_cleanup_inode(struct inode *inode);
 void fsverity_readahead(struct fsverity_info *vi, pgoff_t index,
-		unsigned long nr_pages);
+			unsigned long nr_pages);
 
 struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index);
 void generic_readahead_merkle_tree(struct inode *inode, pgoff_t index,
diff --git a/mm/readahead.c b/mm/readahead.c
index 25f81124beb60..f43d03558e625 100644
--- a/mm/readahead.c
+++ b/mm/readahead.c
@@ -205,8 +205,8 @@ static struct folio *ractl_alloc_folio(struct readahead_control *ractl,
  * or page_cache_sync_readahead() instead.
  *
  * Context: File is referenced by caller, and ractl->mapping->invalidate_lock
- * must be held by the caller in shared mode.  Mutexes may be held by caller.
- * May sleep, but will not reenter filesystem to reclaim memory.
+ * must be held by the caller at least in shared mode.  Mutexes may be held by
+ * caller.  May sleep, but will not reenter filesystem to reclaim memory.
  */
 void page_cache_ra_unbounded(struct readahead_control *ractl,
 		unsigned long nr_to_read, unsigned long lookahead_size)
@@ -229,7 +229,7 @@ void page_cache_ra_unbounded(struct readahead_control *ractl,
 	 */
 	unsigned int nofs = memalloc_nofs_save();
 
-	lockdep_assert_held_read(&mapping->invalidate_lock);
+	lockdep_assert_held(&mapping->invalidate_lock);
 
 	trace_page_cache_ra_unbounded(mapping->host, index, nr_to_read,
 				      lookahead_size);

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

* Re: fsverity speedup and memory usage optimization v5
  2026-02-02 21:14 ` fsverity speedup and memory usage optimization v5 Eric Biggers
@ 2026-02-02 22:34   ` Eric Biggers
  2026-02-03  5:36     ` Christoph Hellwig
  2026-02-04 14:54   ` Matthew Wilcox
  1 sibling, 1 reply; 28+ messages in thread
From: Eric Biggers @ 2026-02-02 22:34 UTC (permalink / raw)
  To: Christoph Hellwig, Jaegeuk Kim, Chao Yu
  Cc: Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Theodore Ts'o, Jaegeuk Kim, Chao Yu, Andrey Albershteyn,
	Matthew Wilcox, linux-fsdevel, linux-btrfs, linux-ext4,
	linux-f2fs-devel, fsverity

On Mon, Feb 02, 2026 at 01:14:23PM -0800, Eric Biggers wrote:
> On Mon, Feb 02, 2026 at 07:06:29AM +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 part optimizes the fsverity 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.
> > 
> > The second part avoids the need for a pointer in every inode for fsverity
> > and instead uses a rhashtable lookup, which is done once per read_folio
> > or ->readahead invocation plus for btrfs only for each bio completion.
> > Right now this does not increse the number of inodes in
> > each slab, but for ext4 we are getting very close to that (within
> > 16 bytes by my count).
> > 
> > Changes since v5:
> >  - drop already merged patches
> >  - fix a bisection hazard for non-ENOENT error returns from
> >    generic_read_merkle_tree_page
> >  - don't recurse on invalidate_lock
> >  - refactor page_cache_ra_unbounded locking to support the above
> >  - refactor ext4 and f2fs fsverity readahead to remove the need for the
> >    first_folio branch in the main readpages loop
> 
> Applied to https://git.kernel.org/pub/scm/fs/fsverity/linux.git/log/?h=for-next
> 
> (Though it's getting late for v6.20 / v7.0.  So if there are any
> additional issues reported, I may have to drop it.)

Unfortunately this silently conflicts with changes in the f2fs tree.
Resolution doesn't look too bad, but we'll need to handle this.
Christoph, Jaegeuk, and Chao, let me know if this looks okay:

diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
index d9085d1236d97..081c441c59e71 100644
--- a/fs/f2fs/data.c
+++ b/fs/f2fs/data.c
@@ -2449,11 +2449,13 @@ static void ffs_detach_free(struct folio *folio)
 	WARN_ON_ONCE(ffs->read_pages_pending != 0);
 	kmem_cache_free(ffs_entry_slab, ffs);
 }
 
 static int f2fs_read_data_large_folio(struct inode *inode,
-		struct readahead_control *rac, struct folio *folio)
+				      struct fsverity_info *vi,
+				      struct readahead_control *rac,
+				      struct folio *folio)
 {
 	struct bio *bio = NULL;
 	sector_t last_block_in_bio = 0;
 	struct f2fs_map_blocks map = {0, };
 	pgoff_t index, offset, next_pgofs = 0;
@@ -2519,13 +2521,12 @@ static int f2fs_read_data_large_folio(struct inode *inode,
 				ret = -EFSCORRUPTED;
 				goto err_out;
 			}
 		} else {
 			folio_zero_range(folio, offset << PAGE_SHIFT, PAGE_SIZE);
-			if (f2fs_need_verity(inode, index) &&
-			    !fsverity_verify_page(folio_file_page(folio,
-								index))) {
+			if (vi && !fsverity_verify_page(
+					  vi, folio_file_page(folio, index))) {
 				ret = -EIO;
 				goto err_out;
 			}
 			continue;
 		}
@@ -2552,14 +2553,14 @@ static int f2fs_read_data_large_folio(struct inode *inode,
 submit_and_realloc:
 			f2fs_submit_read_bio(F2FS_I_SB(inode), bio, DATA);
 			bio = NULL;
 		}
 		if (bio == NULL)
-			bio = f2fs_grab_read_bio(inode, block_nr,
-					max_nr_pages,
-					f2fs_ra_op_flags(rac),
-					index, false);
+			bio = f2fs_grab_read_bio(inode, vi, block_nr,
+						 max_nr_pages,
+						 f2fs_ra_op_flags(rac), index,
+						 false);
 
 		/*
 		 * If the page is under writeback, we need to wait for
 		 * its completion to see the correct decrypted data.
 		 */
@@ -2627,11 +2628,11 @@ static int f2fs_mpage_readpages(struct inode *inode, struct fsverity_info *vi,
 	struct address_space *mapping = rac ? rac->mapping : folio->mapping;
 	unsigned max_nr_pages = nr_pages;
 	int ret = 0;
 
 	if (mapping_large_folio_support(mapping))
-		return f2fs_read_data_large_folio(inode, rac, folio);
+		return f2fs_read_data_large_folio(inode, vi, rac, folio);
 
 #ifdef CONFIG_F2FS_FS_COMPRESSION
 	if (f2fs_compressed_file(inode)) {
 		index = rac ? readahead_index(rac) : folio->index;
 		max_nr_pages = round_up(index + nr_pages, cc.cluster_size) -

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

* Re: [PATCH 03/11] ext4: move ->read_folio and ->readahead to readahead.c
  2026-02-02  6:06 ` [PATCH 03/11] ext4: move ->read_folio and ->readahead to readahead.c Christoph Hellwig
  2026-02-02 13:32   ` Jan Kara
@ 2026-02-03  0:57   ` Theodore Tso
  1 sibling, 0 replies; 28+ messages in thread
From: Theodore Tso @ 2026-02-03  0:57 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: Eric Biggers, Al Viro, Christian Brauner, Jan Kara, David Sterba,
	Jaegeuk Kim, Chao Yu, Andrey Albershteyn, Matthew Wilcox,
	linux-fsdevel, linux-btrfs, linux-ext4, linux-f2fs-devel,
	fsverity

On Mon, Feb 02, 2026 at 07:06:32AM +0100, Christoph Hellwig wrote:
> Keep all the read into pagecache code in a single file.
> 
> Signed-off-by: Christoph Hellwig <hch@lst.de>

Makes sense to me.  Thanks,

Acked-by: Theodore Ts'o <tytso@mit.edu>


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

* Re: fsverity speedup and memory usage optimization v5
  2026-02-02 22:34   ` Eric Biggers
@ 2026-02-03  5:36     ` Christoph Hellwig
  0 siblings, 0 replies; 28+ messages in thread
From: Christoph Hellwig @ 2026-02-03  5:36 UTC (permalink / raw)
  To: Eric Biggers
  Cc: Christoph Hellwig, Jaegeuk Kim, Chao Yu, Al Viro,
	Christian Brauner, Jan Kara, David Sterba, Theodore Ts'o,
	Andrey Albershteyn, Matthew Wilcox, linux-fsdevel, linux-btrfs,
	linux-ext4, linux-f2fs-devel, fsverity

On Mon, Feb 02, 2026 at 02:34:04PM -0800, Eric Biggers wrote:
> On Mon, Feb 02, 2026 at 01:14:23PM -0800, Eric Biggers wrote:
> > On Mon, Feb 02, 2026 at 07:06:29AM +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 part optimizes the fsverity 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.
> > > 
> > > The second part avoids the need for a pointer in every inode for fsverity
> > > and instead uses a rhashtable lookup, which is done once per read_folio
> > > or ->readahead invocation plus for btrfs only for each bio completion.
> > > Right now this does not increse the number of inodes in
> > > each slab, but for ext4 we are getting very close to that (within
> > > 16 bytes by my count).
> > > 
> > > Changes since v5:
> > >  - drop already merged patches
> > >  - fix a bisection hazard for non-ENOENT error returns from
> > >    generic_read_merkle_tree_page
> > >  - don't recurse on invalidate_lock
> > >  - refactor page_cache_ra_unbounded locking to support the above
> > >  - refactor ext4 and f2fs fsverity readahead to remove the need for the
> > >    first_folio branch in the main readpages loop
> > 
> > Applied to https://git.kernel.org/pub/scm/fs/fsverity/linux.git/log/?h=for-next
> > 
> > (Though it's getting late for v6.20 / v7.0.  So if there are any
> > additional issues reported, I may have to drop it.)
> 
> Unfortunately this silently conflicts with changes in the f2fs tree.
> Resolution doesn't look too bad, but we'll need to handle this.
> Christoph, Jaegeuk, and Chao, let me know if this looks okay:

I ended up with the same merge, and it looks good to me.


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

* Re: fsverity speedup and memory usage optimization v5
  2026-02-02 21:14 ` fsverity speedup and memory usage optimization v5 Eric Biggers
  2026-02-02 22:34   ` Eric Biggers
@ 2026-02-04 14:54   ` Matthew Wilcox
  2026-02-04 19:02     ` Eric Biggers
  1 sibling, 1 reply; 28+ messages in thread
From: Matthew Wilcox @ 2026-02-04 14:54 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

On Mon, Feb 02, 2026 at 01:14:23PM -0800, Eric Biggers wrote:
> - Used the code formatting from 'git clang-format' in the cases where it
>   looks better than the ad-hoc formatting

clang-format makes some bad choices.

>  static int ext4_mpage_readpages(struct inode *inode, struct fsverity_info *vi,
> -		struct readahead_control *rac, struct folio *folio)
> +				struct readahead_control *rac,
> +				struct folio *folio)

Aligning to the opening bracket is one of them.  If anything changes
in a subsequent patch (eg function name, whether or not it's static,
adding a function attribute like __must_check, converting the return
type from int to bool), you have to eitheer break the formatting or
needlessly change the lines which have the subsequent arguments.

Also, you've consumed an extra line in this case.  Just leave the
two tab indent, it's actually easier to read.


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

* Re: fsverity speedup and memory usage optimization v5
  2026-02-04 14:54   ` Matthew Wilcox
@ 2026-02-04 19:02     ` Eric Biggers
  2026-02-04 19:09       ` Matthew Wilcox
  0 siblings, 1 reply; 28+ messages in thread
From: Eric Biggers @ 2026-02-04 19:02 UTC (permalink / raw)
  To: Matthew Wilcox
  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

On Wed, Feb 04, 2026 at 02:54:18PM +0000, Matthew Wilcox wrote:
> On Mon, Feb 02, 2026 at 01:14:23PM -0800, Eric Biggers wrote:
> > - Used the code formatting from 'git clang-format' in the cases where it
> >   looks better than the ad-hoc formatting
> 
> clang-format makes some bad choices.
> 
> >  static int ext4_mpage_readpages(struct inode *inode, struct fsverity_info *vi,
> > -		struct readahead_control *rac, struct folio *folio)
> > +				struct readahead_control *rac,
> > +				struct folio *folio)
> 
> Aligning to the opening bracket is one of them.  If anything changes
> in a subsequent patch (eg function name, whether or not it's static,
> adding a function attribute like __must_check, converting the return
> type from int to bool), you have to eitheer break the formatting or
> needlessly change the lines which have the subsequent arguments.
> 
> Also, you've consumed an extra line in this case.  Just leave the
> two tab indent, it's actually easier to read.

Aligning to the opening bracket is the usual style as agreed on by the
kernel community.  This should also be clear if you look at the existing
style in all the files this patchset touches.  It's not done exclusively
but is the more common way.  clang-format just follows that.

It's 2026.  We generally shouldn't be formatting code manually.  We have
better things to do.

If you're going to insist on ad-hoc formatting of argument lists, you'll
need to be more specific about where and how you want it to be done.  It
certainly doesn't make sense in files that are already using the normal
style exclusively, for example.

- Eric

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

* Re: fsverity speedup and memory usage optimization v5
  2026-02-04 19:02     ` Eric Biggers
@ 2026-02-04 19:09       ` Matthew Wilcox
  2026-02-04 19:37         ` Eric Biggers
  0 siblings, 1 reply; 28+ messages in thread
From: Matthew Wilcox @ 2026-02-04 19:09 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

On Wed, Feb 04, 2026 at 11:02:18AM -0800, Eric Biggers wrote:
> Aligning to the opening bracket is the usual style as agreed on by the
> kernel community.

Says who?  I've been part of the kernel community since 1997.  I've
never heard of such a thing.

> It's 2026.  We generally shouldn't be formatting code manually.  We have
> better things to do.

I agree!  Stop changing it unnecessarily.

> If you're going to insist on ad-hoc formatting of argument lists, you'll
> need to be more specific about where and how you want it to be done.  It

Two tabs.  That's it.



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

* Re: fsverity speedup and memory usage optimization v5
  2026-02-04 19:09       ` Matthew Wilcox
@ 2026-02-04 19:37         ` Eric Biggers
  0 siblings, 0 replies; 28+ messages in thread
From: Eric Biggers @ 2026-02-04 19:37 UTC (permalink / raw)
  To: Matthew Wilcox
  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

On Wed, Feb 04, 2026 at 07:09:41PM +0000, Matthew Wilcox wrote:
> On Wed, Feb 04, 2026 at 11:02:18AM -0800, Eric Biggers wrote:
> > Aligning to the opening bracket is the usual style as agreed on by the
> > kernel community.
> 
> Says who?  I've been part of the kernel community since 1997.  I've
> never heard of such a thing.
> 
> > It's 2026.  We generally shouldn't be formatting code manually.  We have
> > better things to do.
> 
> I agree!  Stop changing it unnecessarily.
> 
> > If you're going to insist on ad-hoc formatting of argument lists, you'll
> > need to be more specific about where and how you want it to be done.  It
> 
> Two tabs.  That's it.

I've changed ext4_mpage_readpages() and f2fs_mpage_readpages() to use
your requested ad-hoc formatting.  What else you want, I'm not sure.  In
the code I maintain I just do it the standard way.

- Eric

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

* Re: [f2fs-dev] [PATCH 01/11] fsverity: don't issue readahead for non-ENOENT errors from __filemap_get_folio
  2026-02-02  6:06 ` [PATCH 01/11] fsverity: don't issue readahead for non-ENOENT errors from __filemap_get_folio Christoph Hellwig
  2026-02-02 13:23   ` Jan Kara
@ 2026-02-17 21:14   ` patchwork-bot+f2fs
  1 sibling, 0 replies; 28+ messages in thread
From: patchwork-bot+f2fs @ 2026-02-17 21:14 UTC (permalink / raw)
  To: Christoph Hellwig
  Cc: ebiggers, fsverity, brauner, tytso, aalbersh, willy,
	linux-f2fs-devel, linux-fsdevel, viro, jaegeuk, dsterba, jack,
	linux-ext4, linux-btrfs

Hello:

This series was applied to jaegeuk/f2fs.git (dev)
by Eric Biggers <ebiggers@kernel.org>:

On Mon,  2 Feb 2026 07:06:30 +0100 you wrote:
> Issuing more reads on errors is not a good idea, especially when the
> most common error here is -ENOMEM.
> 
> Signed-off-by: Christoph Hellwig <hch@lst.de>
> ---
>  fs/verity/pagecache.c | 7 ++++---
>  1 file changed, 4 insertions(+), 3 deletions(-)

Here is the summary with links:
  - [f2fs-dev,01/11] fsverity: don't issue readahead for non-ENOENT errors from __filemap_get_folio
    https://git.kernel.org/jaegeuk/f2fs/c/23eec9fd64b2
  - [f2fs-dev,02/11] readahead: push invalidate_lock out of page_cache_ra_unbounded
    (no matching commit)
  - [f2fs-dev,03/11] ext4: move ->read_folio and ->readahead to readahead.c
    (no matching commit)
  - [f2fs-dev,04/11] fsverity: kick off hash readahead at data I/O submission time
    (no matching commit)
  - [f2fs-dev,05/11] fsverity: deconstify the inode pointer in struct fsverity_info
    https://git.kernel.org/jaegeuk/f2fs/c/7e36e044958d
  - [f2fs-dev,06/11] fsverity: push out fsverity_info lookup
    (no matching commit)
  - [f2fs-dev,07/11] fs: consolidate fsverity_info lookup in buffer.c
    https://git.kernel.org/jaegeuk/f2fs/c/f6ae956dfb34
  - [f2fs-dev,08/11] ext4: consolidate fsverity_info lookup
    (no matching commit)
  - [f2fs-dev,09/11] f2fs: consolidate fsverity_info lookup
    (no matching commit)
  - [f2fs-dev,10/11] btrfs: consolidate fsverity_info lookup
    https://git.kernel.org/jaegeuk/f2fs/c/b0160e4501bb
  - [f2fs-dev,11/11] fsverity: use a hashtable to find the fsverity_info
    (no matching commit)

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



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

end of thread, other threads:[~2026-02-17 21:14 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-02  6:06 fsverity speedup and memory usage optimization v5 Christoph Hellwig
2026-02-02  6:06 ` [PATCH 01/11] fsverity: don't issue readahead for non-ENOENT errors from __filemap_get_folio Christoph Hellwig
2026-02-02 13:23   ` Jan Kara
2026-02-17 21:14   ` [f2fs-dev] " patchwork-bot+f2fs
2026-02-02  6:06 ` [PATCH 02/11] readahead: push invalidate_lock out of page_cache_ra_unbounded Christoph Hellwig
2026-02-02 13:36   ` Jan Kara
2026-02-02 15:11   ` Matthew Wilcox
2026-02-02 15:17     ` Christoph Hellwig
2026-02-02 21:04       ` Eric Biggers
2026-02-02  6:06 ` [PATCH 03/11] ext4: move ->read_folio and ->readahead to readahead.c Christoph Hellwig
2026-02-02 13:32   ` Jan Kara
2026-02-03  0:57   ` Theodore Tso
2026-02-02  6:06 ` [PATCH 04/11] fsverity: kick off hash readahead at data I/O submission time Christoph Hellwig
2026-02-02  6:06 ` [PATCH 05/11] fsverity: deconstify the inode pointer in struct fsverity_info Christoph Hellwig
2026-02-02  6:06 ` [PATCH 06/11] fsverity: push out fsverity_info lookup Christoph Hellwig
2026-02-02  6:06 ` [PATCH 07/11] fs: consolidate fsverity_info lookup in buffer.c Christoph Hellwig
2026-02-02 13:38   ` Jan Kara
2026-02-02  6:06 ` [PATCH 08/11] ext4: consolidate fsverity_info lookup Christoph Hellwig
2026-02-02  6:06 ` [PATCH 09/11] f2fs: " Christoph Hellwig
2026-02-02  6:06 ` [PATCH 10/11] btrfs: " Christoph Hellwig
2026-02-02  6:06 ` [PATCH 11/11] fsverity: use a hashtable to find the fsverity_info Christoph Hellwig
2026-02-02 21:14 ` fsverity speedup and memory usage optimization v5 Eric Biggers
2026-02-02 22:34   ` Eric Biggers
2026-02-03  5:36     ` Christoph Hellwig
2026-02-04 14:54   ` Matthew Wilcox
2026-02-04 19:02     ` Eric Biggers
2026-02-04 19:09       ` Matthew Wilcox
2026-02-04 19:37         ` Eric Biggers

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