ANDROID: Fuse: Fix Passthrough and Cache Coherency

Fuse writeback and passthrough have some interactions that require
additional handling.

If there is cached data from previous reads, and a passthrough write
happens, the relevant sections of the cache should be invalidated.
Otherwise the new data is inaccessible to non-passthrough until the
cache otherwise expires.

If there are dirty pages, passthrough read would not be aware of these
changes. First, they must be flushed so that the lower filesystem will
reflect the change. This can't be done directly since there may be
specific logic about how that data gets written that fuse itself does
not know about.

This also enables mmap for passthrough and nonpassthrough at the same
time. Note that if a shared mmap is open for both passthrough and
nonpassthrough, there will be some inconsistencies between data seen on
both, although interactions between file read and write are handled.
This is an improvement on the existing Android interaction.

Change-Id: I45c3efe92849a88ffcf586a1ec6508fed632e1d0
Bug: 372951405
Test: atest android.scopedstorage.cts.general.ScopedStorageDeviceTest
Signed-off-by: Daniel Rosenberg <drosen@google.com>
This commit is contained in:
Daniel Rosenberg
2025-02-04 16:23:52 -08:00
parent e878b35d3d
commit 412d297361
2 changed files with 17 additions and 0 deletions
+8
View File
@@ -2701,8 +2701,16 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
*/
if (fuse_file_passthrough(ff))
return fuse_passthrough_mmap(file, vma);
/*
* Old Android passthrough did not handle this case, but did allow the mmap to continue.
* This will not cleanly handle the case of a shared mmap across passthrough and
* nonpassthrough at the same time, although shared mmap through cache and file io through
* the lower filesystem should work as expected, at a performance penalty.
*/
#if 0
else if (fuse_inode_backing(get_fuse_inode(inode)))
return -ENODEV;
#endif
/*
* FOPEN_DIRECT_IO handling is special compared to O_DIRECT,
+9
View File
@@ -10,6 +10,7 @@
#include <linux/file.h>
#include <linux/backing-file.h>
#include <linux/splice.h>
#include <linux/pagemap.h>
static void fuse_file_accessed(struct file *file)
{
@@ -34,6 +35,10 @@ static void fuse_passthrough_end_write(struct file *file, loff_t pos, ssize_t re
inode->i_blocks = backing_inode->i_blocks;
i_size_write(inode, i_size_read(backing_inode));
}
if (ret > 0) {
invalidate_inode_pages2_range(inode->i_mapping,
(pos - ret) >> PAGE_SHIFT, pos >> PAGE_SHIFT);
}
}
ssize_t fuse_passthrough_read_iter(struct kiocb *iocb, struct iov_iter *iter)
@@ -56,6 +61,8 @@ ssize_t fuse_passthrough_read_iter(struct kiocb *iocb, struct iov_iter *iter)
if (!count)
return 0;
/* Flush any dirtied cache pages from fuse cache */
write_inode_now(file_inode(file), 1);
ret = backing_file_read_iter(backing_file, iter, iocb, iocb->ki_flags,
&ctx);
@@ -106,6 +113,8 @@ ssize_t fuse_passthrough_splice_read(struct file *in, loff_t *ppos,
pr_debug("%s: backing_file=0x%p, pos=%lld, len=%zu, flags=0x%x\n", __func__,
backing_file, ppos ? *ppos : 0, len, flags);
/* Flush any dirtied cache pages from fuse cache */
write_inode_now(file_inode(in), 1);
return backing_file_splice_read(backing_file, ppos, pipe, len, flags,
&ctx);
}