[PATCH] fuse: fix hang on SMP
Fuse didn't always call i_size_write() with i_mutex held which caused rare
hangs on SMP/32bit. This bug has been present since fuse-2.2, well before
being merged into mainline.
The simplest solution is to protect i_size_write() with the per-connection
spinlock. Using i_mutex for this purpose would require some restructuring of
the code and I'm not even sure it's always safe to acquire i_mutex in all
places i_size needs to be set.
Since most of vmtruncate is already duplicated for other reasons, duplicate
the remaining part as well, making all i_size_write() calls internal to fuse.
Using i_size_write() was unnecessary in fuse_init_inode(), since this function
is only called on a newly created locked inode.
Reported by a few people over the years, but special thanks to Dana Henriksen
who was persistent enough in helping me debug it.
Signed-off-by: Miklos Szeredi <miklos@szeredi.hu>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 7d0a9ae..8e10616 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -109,6 +109,7 @@
void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr)
{
+ struct fuse_conn *fc = get_fuse_conn(inode);
if (S_ISREG(inode->i_mode) && i_size_read(inode) != attr->size)
invalidate_inode_pages(inode->i_mapping);
@@ -117,7 +118,9 @@
inode->i_nlink = attr->nlink;
inode->i_uid = attr->uid;
inode->i_gid = attr->gid;
+ spin_lock(&fc->lock);
i_size_write(inode, attr->size);
+ spin_unlock(&fc->lock);
inode->i_blocks = attr->blocks;
inode->i_atime.tv_sec = attr->atime;
inode->i_atime.tv_nsec = attr->atimensec;
@@ -130,7 +133,7 @@
static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr)
{
inode->i_mode = attr->mode & S_IFMT;
- i_size_write(inode, attr->size);
+ inode->i_size = attr->size;
if (S_ISREG(inode->i_mode)) {
fuse_init_common(inode);
fuse_init_file_inode(inode);