When v9fs_stat2inode() is invoked concurrently under 32-bit case and CONFIG_LBDAF is enabled, multiple updates of inode->i_blocks may corrupt the value of i_blocks because the assignment of 64-bit value under 32-bit host is not atomic. Fix it by using i_lock to protect update of inode->i_blocks. Also Skip the update when it's not requested. Suggested-by: Dominique Martinet <asmadeus@xxxxxxxxxxxxx> Signed-off-by: Hou Tao <houtao1@xxxxxxxxxx> --- fs/9p/v9fs_vfs.h | 10 ++++++++++ fs/9p/vfs_inode.c | 7 ++++--- fs/9p/vfs_inode_dotl.c | 16 +++++++++------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/fs/9p/v9fs_vfs.h b/fs/9p/v9fs_vfs.h index 3d371b9e461a..0bd4acfdb6af 100644 --- a/fs/9p/v9fs_vfs.h +++ b/fs/9p/v9fs_vfs.h @@ -101,4 +101,14 @@ static inline void v9fs_i_size_write(struct inode *inode, loff_t i_size) if (sizeof(i_size) > sizeof(long)) spin_unlock(&inode->i_lock); } + +static inline void v9fs_i_blocks_write(struct inode *inode, blkcnt_t i_blocks) +{ + /* Avoid taking i_lock under 64-bits */ + if (sizeof(i_blocks) > sizeof(long)) + spin_lock(&inode->i_lock); + inode->i_blocks = i_blocks; + if (sizeof(i_blocks) > sizeof(long)) + spin_unlock(&inode->i_lock); +} #endif diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 72b779bc0942..f05ff3cfbfe7 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -1218,10 +1218,11 @@ v9fs_stat2inode(struct p9_wstat *stat, struct inode *inode, mode |= inode->i_mode & ~S_IALLUGO; inode->i_mode = mode; - if (!(flags & V9FS_STAT2INODE_KEEP_ISIZE)) + if (!(flags & V9FS_STAT2INODE_KEEP_ISIZE)) { v9fs_i_size_write(inode, stat->length); - /* not real number of blocks, but 512 byte ones ... */ - inode->i_blocks = (stat->length + 512 - 1) >> 9; + /* not real number of blocks, but 512 byte ones ... */ + v9fs_i_blocks_write(inode, (stat->length + 512 - 1) >> 9); + } v9inode->cache_validity &= ~V9FS_INO_INVALID_ATTR; } diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index a950a927a626..c1e2416d83c1 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -633,9 +633,10 @@ v9fs_stat2inode_dotl(struct p9_stat_dotl *stat, struct inode *inode, mode |= inode->i_mode & ~S_IALLUGO; inode->i_mode = mode; - if (!(flags & V9FS_STAT2INODE_KEEP_ISIZE)) + if (!(flags & V9FS_STAT2INODE_KEEP_ISIZE)) { v9fs_i_size_write(inode, stat->st_size); - inode->i_blocks = stat->st_blocks; + v9fs_i_blocks_write(inode, stat->st_blocks); + } } else { if (stat->st_result_mask & P9_STATS_ATIME) { inode->i_atime.tv_sec = stat->st_atime_sec; @@ -664,11 +665,12 @@ v9fs_stat2inode_dotl(struct p9_stat_dotl *stat, struct inode *inode, } if (stat->st_result_mask & P9_STATS_RDEV) inode->i_rdev = new_decode_dev(stat->st_rdev); - if (!(flags & V9FS_STAT2INODE_KEEP_ISIZE) && - stat->st_result_mask & P9_STATS_SIZE) - v9fs_i_size_write(inode, stat->st_size); - if (stat->st_result_mask & P9_STATS_BLOCKS) - inode->i_blocks = stat->st_blocks; + if (!(flags & V9FS_STAT2INODE_KEEP_ISIZE)) { + if (stat->st_result_mask & P9_STATS_SIZE) + v9fs_i_size_write(inode, stat->st_size); + if (stat->st_result_mask & P9_STATS_BLOCKS) + v9fs_i_blocks_write(inode, stat->st_blocks); + } } if (stat->st_result_mask & P9_STATS_GEN) inode->i_generation = stat->st_gen; -- 2.16.2.dirty