aboutsummaryrefslogtreecommitdiff
path: root/fs/fuse/dev.c
diff options
context:
space:
mode:
authorMiklos Szeredi2006-04-11 21:14:26 +0200
committerMiklos Szeredi2006-04-11 21:14:26 +0200
commit73ce8355c243a434524a34c05cc417dd0467996e (patch)
treea5bc5bfb31c41a5806caf763533943e7411e6543 /fs/fuse/dev.c
parent2514395ef88b46e895726a8d40966cb83de7940c (diff)
[fuse] fix deadlock between fuse_put_super() and request_end()
A deadlock was possible, when the last reference to the superblock was held due to a background request containing a file reference. Releasing the file would release the vfsmount which in turn would release the superblock. Since sbput_sem is held during the fput() and fuse_put_super() tries to acquire this same semaphore, a deadlock results. The chosen soltuion is to get rid of sbput_sem, and instead use the spinlock to ensure the referenced inodes/file are released only once. Since the actual release may sleep, defer these outside the locked region, but using local variables instead of the structure members. This is a much more rubust solution. Signed-off-by: Miklos Szeredi <miklos@szeredi.hu>
Diffstat (limited to 'fs/fuse/dev.c')
-rw-r--r--fs/fuse/dev.c28
1 files changed, 16 insertions, 12 deletions
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 6c740f860665..d4efb6223e2c 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -120,20 +120,14 @@ void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req)
}
}
-void fuse_release_background(struct fuse_conn *fc, struct fuse_req *req)
+void fuse_remove_background(struct fuse_conn *fc, struct fuse_req *req)
{
- iput(req->inode);
- iput(req->inode2);
- if (req->file)
- fput(req->file);
- spin_lock(&fc->lock);
- list_del(&req->bg_entry);
+ list_del_init(&req->bg_entry);
if (fc->num_background == FUSE_MAX_BACKGROUND) {
fc->blocked = 0;
wake_up_all(&fc->blocked_waitq);
}
fc->num_background--;
- spin_unlock(&fc->lock);
}
/*
@@ -163,17 +157,27 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req)
wake_up(&req->waitq);
fuse_put_request(fc, req);
} else {
+ struct inode *inode = req->inode;
+ struct inode *inode2 = req->inode2;
+ struct file *file = req->file;
void (*end) (struct fuse_conn *, struct fuse_req *) = req->end;
req->end = NULL;
+ req->inode = NULL;
+ req->inode2 = NULL;
+ req->file = NULL;
+ if (!list_empty(&req->bg_entry))
+ fuse_remove_background(fc, req);
spin_unlock(&fc->lock);
- down_read(&fc->sbput_sem);
- if (fc->mounted)
- fuse_release_background(fc, req);
- up_read(&fc->sbput_sem);
+
if (end)
end(fc, req);
else
fuse_put_request(fc, req);
+
+ if (file)
+ fput(file);
+ iput(inode);
+ iput(inode2);
}
}