From 3a69962ef7ea8d816160bda50aef0b47acbb9762 Mon Sep 17 00:00:00 2001 From: Horst Birthelmer Date: Wed, 25 Feb 2026 18:26:22 +0100 Subject: [PATCH] fuse: fix wrong ff->iomode state changes from parallel dio write This is a backport of upstream commit: 4864a6dd8320ad856698f93009c89f66ccb1653f But due to the fact that Linux 6.8 does not have the passthrough feature that commit cannot be cherry picked without a de facto rewrite. (E.g. the struct fuse_backed is not even defined) Signed-off-by: Horst Birthelmer --- fs/fuse/file.c | 8 ++++---- fs/fuse/fuse_i.h | 2 ++ fs/fuse/inode.c | 3 +++ fs/fuse/iomode.c | 51 ++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index f447b00b93e40c..7ad8fa49e16e9c 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1430,7 +1430,7 @@ static void fuse_dio_lock(struct kiocb *iocb, struct iov_iter *from, bool *exclusive) { struct inode *inode = file_inode(iocb->ki_filp); - struct fuse_file *ff = iocb->ki_filp->private_data; + struct fuse_inode *fi = get_fuse_inode(inode); *exclusive = fuse_dio_wr_exclusive_lock(iocb, from); if (*exclusive) { @@ -1445,7 +1445,7 @@ static void fuse_dio_lock(struct kiocb *iocb, struct iov_iter *from, * have raced, so check it again. */ if (fuse_io_past_eof(iocb, from) || - fuse_file_uncached_io_start(inode, ff) != 0) { + fuse_inode_uncached_io_start(fi) != 0) { inode_unlock_shared(inode); inode_lock(inode); *exclusive = true; @@ -1456,13 +1456,13 @@ static void fuse_dio_lock(struct kiocb *iocb, struct iov_iter *from, static void fuse_dio_unlock(struct kiocb *iocb, bool exclusive) { struct inode *inode = file_inode(iocb->ki_filp); - struct fuse_file *ff = iocb->ki_filp->private_data; + struct fuse_inode *fi = get_fuse_inode(inode); if (exclusive) { inode_unlock(inode); } else { /* Allow opens in caching mode after last parallel dio end */ - fuse_file_uncached_io_end(inode, ff); + fuse_inode_uncached_io_end(fi); inode_unlock_shared(inode); } } diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 4f0474e2e31def..326ab477d4dd55 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1465,6 +1465,8 @@ int fuse_fileattr_set(struct mnt_idmap *idmap, /* iomode.c */ int fuse_file_cached_io_start(struct inode *inode, struct fuse_file *ff); +int fuse_inode_uncached_io_start(struct fuse_inode *fi); +void fuse_inode_uncached_io_end(struct fuse_inode *fi); int fuse_file_uncached_io_start(struct inode *inode, struct fuse_file *ff); void fuse_file_uncached_io_end(struct inode *inode, struct fuse_file *ff); diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 61962fd615857a..2a44160d74538d 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -174,6 +174,9 @@ static void fuse_evict_inode(struct inode *inode) fuse_cleanup_submount_lookup(fc, fi->submount_lookup); fi->submount_lookup = NULL; } + if (S_ISREG(inode->i_mode) && !fuse_is_bad(inode)) { + WARN_ON(fi->iocachectr != 0); + } /* * Evict of non-deleted inode may race with outstanding * LOOKUP/READDIRPLUS requests and result in inconsistency when diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c index ea47c76b9df114..c0496b971da0cb 100644 --- a/fs/fuse/iomode.c +++ b/fs/fuse/iomode.c @@ -70,10 +70,12 @@ static void fuse_file_cached_io_end(struct inode *inode, struct fuse_file *ff) spin_unlock(&fi->lock); } -/* Start strictly uncached io mode where cache access is not allowed */ -int fuse_file_uncached_io_start(struct inode *inode, struct fuse_file *ff) +/* + * Start strictly uncached io mode where cache access is not allowed. + * This is for parallel DIO - does NOT change ff->iomode state. + */ +int fuse_inode_uncached_io_start(struct fuse_inode *fi) { - struct fuse_inode *fi = get_fuse_inode(inode); int err = 0; spin_lock(&fi->lock); @@ -81,28 +83,57 @@ int fuse_file_uncached_io_start(struct inode *inode, struct fuse_file *ff) err = -ETXTBSY; goto unlock; } - WARN_ON(ff->iomode != IOM_NONE); fi->iocachectr--; - ff->iomode = IOM_UNCACHED; unlock: spin_unlock(&fi->lock); return err; } -void fuse_file_uncached_io_end(struct inode *inode, struct fuse_file *ff) +/* + * End uncached io mode for parallel DIO. + * Does NOT change ff->iomode state. + */ +void fuse_inode_uncached_io_end(struct fuse_inode *fi) { - struct fuse_inode *fi = get_fuse_inode(inode); - spin_lock(&fi->lock); WARN_ON(fi->iocachectr >= 0); - WARN_ON(ff->iomode != IOM_UNCACHED); - ff->iomode = IOM_NONE; fi->iocachectr++; if (!fi->iocachectr) wake_up(&fi->direct_io_waitq); spin_unlock(&fi->lock); } +/* + * Start uncached io mode for file open. + * Takes uncached_io inode mode reference AND sets ff->iomode. + */ +int fuse_file_uncached_io_start(struct inode *inode, struct fuse_file *ff) +{ + struct fuse_inode *fi = get_fuse_inode(inode); + int err; + + err = fuse_inode_uncached_io_start(fi); + if (err) + return err; + + WARN_ON(ff->iomode != IOM_NONE); + ff->iomode = IOM_UNCACHED; + return 0; +} + +/* + * End uncached io mode for file release. + * Drops uncached_io inode mode reference AND clears ff->iomode. + */ +void fuse_file_uncached_io_end(struct inode *inode, struct fuse_file *ff) +{ + struct fuse_inode *fi = get_fuse_inode(inode); + + WARN_ON(ff->iomode != IOM_UNCACHED); + ff->iomode = IOM_NONE; + fuse_inode_uncached_io_end(fi); +} + /* Request access to submit new io to inode via open file */ int fuse_file_io_open(struct file *file, struct inode *inode) {