When the atomic_o_trunc flag is set, fuse_do_setattr skips most of its logic, including truncating the page cache. Run the same invalidation logic in fuse_finish_open when truncating. This fixes a bug where open(O_TRUNC) followed by a sequence of writes does not flush the page cache, resulting in an mmap longer than the new file's length (from another process) not properly zero-filling the last page. Does fuse_finish_open() need additional locking? I am not sure of the locking requirements of truncate_pagecache or invalidate_inode_pages2. The following C++ program reproduces the bug. The FUSE daemon must have enabled the ATOMIC_O_TRUNC flag https://gist.github.com/chadaustin/8e4ba1e2cd2d023ff6f9eff921eb8bfc Signed-off-by: Chad Austin <chadaustin@xxxxxx> --- fs/fuse/dir.c | 7 ++----- fs/fuse/file.c | 12 ++++++++++++ fs/fuse/fuse_i.h | 2 ++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 2496738..c387171 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1698,11 +1698,8 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr, * Only call invalidate_inode_pages2() after removing * FUSE_NOWRITE, otherwise fuse_launder_page() would deadlock. */ - if ((is_truncate || !is_wb) && - S_ISREG(inode->i_mode) && oldsize != outarg.attr.size) { - truncate_pagecache(inode, outarg.attr.size); - invalidate_inode_pages2(inode->i_mapping); - } + if ((is_truncate || !is_wb) && oldsize != outarg.attr.size) + fuse_truncate_inode_cache(inode, outarg.attr.size); clear_bit(FUSE_I_SIZE_UNSTABLE, &fi->state); return 0; diff --git a/fs/fuse/file.c b/fs/fuse/file.c index cb7dff5..cb1968e 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -188,6 +188,10 @@ void fuse_finish_open(struct inode *inode, struct file *file) i_size_write(inode, 0); spin_unlock(&fc->lock); fuse_invalidate_attr(inode); + + if (!fc->writeback_cache) + fuse_truncate_inode_cache(inode, 0); + if (fc->writeback_cache) file_update_time(file); } @@ -195,6 +199,14 @@ void fuse_finish_open(struct inode *inode, struct file *file) fuse_link_write_file(file); } +void fuse_truncate_inode_cache(struct inode *inode, loff_t newsize) +{ + if (S_ISREG(inode->i_mode)) { + truncate_pagecache(inode, newsize); + invalidate_inode_pages2(inode->i_mapping); + } +} + int fuse_open_common(struct inode *inode, struct file *file, bool isdir) { struct fuse_conn *fc = get_fuse_conn(inode); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index d5773ca..47baea4 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -913,6 +913,8 @@ void fuse_release_nowrite(struct inode *inode); u64 fuse_get_attr_version(struct fuse_conn *fc); +void fuse_truncate_inode_cache(struct inode *inode, loff_t newsize); + /** * File-system tells the kernel to invalidate cache for the given node id. */ -- 2.9.5