From: Bernd Schubert bschubert@ddn.com
[ Upstream commit b359af8275a982a458e8df6c6beab1415be1f795 ]
generic_file_direct_write() also does this and has a large comment about.
Reproducer here is xfstest's generic/209, which is exactly to have competing DIO write and cached IO read.
Signed-off-by: Bernd Schubert bschubert@ddn.com Signed-off-by: Miklos Szeredi mszeredi@redhat.com Signed-off-by: Sasha Levin sashal@kernel.org ---
LLM Generated explanations, may be completely bogus:
## Analysis Summary
### The Bug
This commit fixes a **data coherency bug** in FUSE when using `FOPEN_DIRECT_IO`. The problem occurs when: 1. A direct I/O write completes on a FUSE filesystem 2. A concurrent buffered read has populated the page cache with (now stale) data 3. Without invalidation, the cached read returns **stale data**
The VFS layer handles this in `generic_file_direct_write()` (mm/filemap.c:4172-4224) by calling `kiocb_invalidate_post_direct_write()` after direct writes. The comment in VFS explicitly mentions this is needed to "invalidate clean pages which might have been cached by non-direct readahead."
### The Fix
The fix adds the same post-write invalidation pattern to FUSE's `fuse_direct_io()` function: - After a successful write (`res > 0 && write && fopen_direct_io`) - Call `invalidate_inode_pages2_range(mapping, idx_from, idx_to)` - This ensures stale read-ahead pages are invalidated
**Code size:** +9 lines, single function, single file
### Stable Kernel Criteria Assessment
| Criterion | Assessment | |-----------|------------| | Obviously correct | ✅ Yes - follows established VFS pattern | | Fixes real bug | ✅ Yes - stale data reads (data integrity) | | Has reproducer | ✅ Yes - xfstest generic/209 | | Small and contained | ✅ Yes - 9 lines in one function | | No new features | ✅ Correct - pure bug fix | | Cc: stable tag | ❌ Not present | | Fixes: tag | ❌ Not present |
### Dependencies and Backport Concerns
**Critical dependency:** This commit requires commit `80e4f25262f9f` ("fuse: invalidate page cache pages before direct write") which introduced the `fopen_direct_io` variable and `idx_from`/`idx_to` calculations. That commit was merged in **v6.6-rc1**.
**Backportable to:** - stable/linux-6.6.y ✅ - stable/linux-6.11.y ✅ - stable/linux-6.12.y ✅
**NOT backportable to:** - stable/linux-6.1.y ❌ (missing prerequisite code) - Earlier LTS kernels ❌
### Risk Assessment
**LOW RISK:** - Very small change (+9 lines) - Uses existing, well-tested API (`invalidate_inode_pages2_range`) - Follows the same pattern as the VFS layer - Error return from invalidation is silently ignored (same as VFS behavior - "if this invalidation fails, tough, the write still worked...") - Only affects FUSE filesystems using `FOPEN_DIRECT_IO` with concurrent cached reads
### User Impact
- **Severity:** Medium-High - stale data reads are a data integrity issue - **Affected users:** FUSE filesystem users enabling `FOPEN_DIRECT_IO` (including some high-performance storage systems) - **Reproducer:** Clear, well-known xfstest (generic/209)
### Verdict
Despite lacking explicit stable tags, this is a legitimate data integrity fix. The bug causes **real user-visible corruption** (stale data reads), the fix is small and surgical, follows an established VFS pattern, and has low regression risk. The lack of `Cc: stable` doesn't disqualify it - many important fixes arrive without explicit tags.
For kernels 6.6+, this should be backported. For earlier kernels, the prerequisite code doesn't exist, so backporting would require additional work.
**YES**
fs/fuse/file.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/fs/fuse/file.c b/fs/fuse/file.c index c5c82b3807911..bb4ecfd469a5e 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1681,6 +1681,15 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter, if (res > 0) *ppos = pos;
+ if (res > 0 && write && fopen_direct_io) { + /* + * As in generic_file_direct_write(), invalidate after the + * write, to invalidate read-ahead cache that may have competed + * with the write. + */ + invalidate_inode_pages2_range(mapping, idx_from, idx_to); + } + return res > 0 ? res : err; } EXPORT_SYMBOL_GPL(fuse_direct_io);