fuse_passthrough_write_iter() unconditionally took the exclusive inode lock, so passthrough writes were fully serialised even when the server advertised FOPEN_PARALLEL_DIRECT_WRITES and the write was direct, non-append and within EOF. Native direct-IO writes already take a shared lock in that case via fuse_dio_lock(); passthrough should do the same. Rather than duplicate the direct-IO locking logic, generalise it and share it between both write paths: - rename fuse_dio_wr_exclusive_lock() to fuse_io_wr_exclusive_lock() and teach it that a passthrough open without FOPEN_DIRECT_IO only qualifies for the shared lock when the write itself is IOCB_DIRECT (buffered passthrough writes keep the exclusive lock); - factor the uncached-io refcount dance into fuse_parallel_dio_start() and fuse_parallel_dio_end(), which are no-ops for the permanent uncached iomode that passthrough opens hold, so the shared-lock past-EOF re-check works unchanged for both paths; - rename fuse_dio_lock()/fuse_dio_unlock() to fuse_io_wr_lock()/ fuse_io_wr_unlock(), export them via fuse_i.h, and call them from fuse_passthrough_write_iter(). No functional change for the native direct-IO write path. Suggested-by: Amir Goldstein Signed-off-by: Russ Fellows --- fs/fuse/file.c | 53 ++++++++++++++++++++++++++++++++++--------- fs/fuse/fuse_i.h | 2 ++ fs/fuse/passthrough.c | 6 ++--- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 79d1b50..449837a 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1349,19 +1349,29 @@ static bool fuse_io_past_eof(struct kiocb *iocb, struct iov_iter *iter) } /* - * @return true if an exclusive lock for direct IO writes is needed + * @return true if an exclusive lock for direct IO or passthrough writes is + * needed */ -static bool fuse_dio_wr_exclusive_lock(struct kiocb *iocb, struct iov_iter *from) +static bool fuse_io_wr_exclusive_lock(struct kiocb *iocb, struct iov_iter *from) { struct file *file = iocb->ki_filp; struct fuse_file *ff = file->private_data; struct inode *inode = file_inode(iocb->ki_filp); struct fuse_inode *fi = get_fuse_inode(inode); + bool fopen_direct_io = ff->open_flags & FOPEN_DIRECT_IO; /* Server side has to advise that it supports parallel dio writes. */ if (!(ff->open_flags & FOPEN_PARALLEL_DIRECT_WRITES)) return true; + /* + * Passthrough opens without FOPEN_DIRECT_IO only support parallel + * writes for I/O that is itself direct (IOCB_DIRECT); buffered + * passthrough writes still need the exclusive lock. + */ + if (!fopen_direct_io && !(iocb->ki_flags & IOCB_DIRECT)) + return true; + /* * Append will need to know the eventual EOF - always needs an * exclusive lock. @@ -1380,13 +1390,33 @@ static bool fuse_dio_wr_exclusive_lock(struct kiocb *iocb, struct iov_iter *from return false; } -static void fuse_dio_lock(struct kiocb *iocb, struct iov_iter *from, - bool *exclusive) +static int fuse_parallel_dio_start(struct fuse_file *ff, struct fuse_inode *fi) +{ + /* Passthrough opens are in permanent uncached mode */ + if (ff->iomode == IOM_UNCACHED) + return 0; + + return fuse_inode_uncached_io_start(fi, NULL); +} + +static void fuse_parallel_dio_end(struct fuse_file *ff, struct fuse_inode *fi) +{ + /* Passthrough opens are in permanent uncached mode */ + if (ff->iomode == IOM_UNCACHED) + return; + + /* Allow opens in caching mode after last parallel dio end */ + fuse_inode_uncached_io_end(fi); +} + +/* Take shared/exclusive inode lock for a direct IO or passthrough write */ +void fuse_io_wr_lock(struct kiocb *iocb, struct iov_iter *from, bool *exclusive) { + struct fuse_file *ff = iocb->ki_filp->private_data; struct inode *inode = file_inode(iocb->ki_filp); struct fuse_inode *fi = get_fuse_inode(inode); - *exclusive = fuse_dio_wr_exclusive_lock(iocb, from); + *exclusive = fuse_io_wr_exclusive_lock(iocb, from); if (*exclusive) { inode_lock(inode); } else { @@ -1399,7 +1429,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_inode_uncached_io_start(fi, NULL) != 0) { + fuse_parallel_dio_start(ff, fi) != 0) { inode_unlock_shared(inode); inode_lock(inode); *exclusive = true; @@ -1407,16 +1437,17 @@ static void fuse_dio_lock(struct kiocb *iocb, struct iov_iter *from, } } -static void fuse_dio_unlock(struct kiocb *iocb, bool exclusive) +/* Release the inode lock taken for a direct IO or passthrough write */ +void fuse_io_wr_unlock(struct kiocb *iocb, bool exclusive) { + struct fuse_file *ff = iocb->ki_filp->private_data; struct inode *inode = file_inode(iocb->ki_filp); 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_inode_uncached_io_end(fi); + fuse_parallel_dio_end(ff, fi); inode_unlock_shared(inode); } } @@ -1759,7 +1790,7 @@ static ssize_t fuse_direct_write_iter(struct kiocb *iocb, struct iov_iter *from) ssize_t res; bool exclusive; - fuse_dio_lock(iocb, from, &exclusive); + fuse_io_wr_lock(iocb, from, &exclusive); res = generic_write_checks(iocb, from); if (res > 0) { task_io_account_write(res); @@ -1773,7 +1804,7 @@ static ssize_t fuse_direct_write_iter(struct kiocb *iocb, struct iov_iter *from) fuse_write_update_attr(inode, iocb->ki_pos, res); } } - fuse_dio_unlock(iocb, exclusive); + fuse_io_wr_unlock(iocb, exclusive); return res; } diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index c288f28..821eccf 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1529,6 +1529,8 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid, unsigned int open_flags, bool isdir); void fuse_file_release(struct inode *inode, struct fuse_file *ff, unsigned int open_flags, fl_owner_t id, bool isdir); +void fuse_io_wr_lock(struct kiocb *iocb, struct iov_iter *from, bool *exclusive); +void fuse_io_wr_unlock(struct kiocb *iocb, bool exclusive); /* backing.c */ #ifdef CONFIG_FUSE_PASSTHROUGH diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c index f2d08ac..2f4fe90 100644 --- a/fs/fuse/passthrough.c +++ b/fs/fuse/passthrough.c @@ -54,11 +54,11 @@ ssize_t fuse_passthrough_write_iter(struct kiocb *iocb, struct iov_iter *iter) { struct file *file = iocb->ki_filp; - struct inode *inode = file_inode(file); struct fuse_file *ff = file->private_data; struct file *backing_file = fuse_file_passthrough(ff); size_t count = iov_iter_count(iter); ssize_t ret; + bool exclusive; struct backing_file_ctx ctx = { .cred = ff->cred, .end_write = fuse_passthrough_end_write, @@ -70,10 +70,10 @@ ssize_t fuse_passthrough_write_iter(struct kiocb *iocb, if (!count) return 0; - inode_lock(inode); + fuse_io_wr_lock(iocb, iter, &exclusive); ret = backing_file_write_iter(backing_file, iter, iocb, iocb->ki_flags, &ctx); - inode_unlock(inode); + fuse_io_wr_unlock(iocb, exclusive); return ret; } -- 2.51.0