summaryrefslogtreecommitdiff
path: root/fs/fuse/dev.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/fuse/dev.c')
-rw-r--r--fs/fuse/dev.c78
1 files changed, 59 insertions, 19 deletions
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index e8f3170946f..ca6fc0e96d7 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -26,7 +26,7 @@ static inline struct fuse_conn *fuse_get_conn(struct file *file)
struct fuse_conn *fc;
spin_lock(&fuse_lock);
fc = file->private_data;
- if (fc && !fc->sb)
+ if (fc && !fc->mounted)
fc = NULL;
spin_unlock(&fuse_lock);
return fc;
@@ -148,6 +148,17 @@ void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req)
fuse_putback_request(fc, req);
}
+void fuse_release_background(struct fuse_req *req)
+{
+ iput(req->inode);
+ iput(req->inode2);
+ if (req->file)
+ fput(req->file);
+ spin_lock(&fuse_lock);
+ list_del(&req->bg_entry);
+ spin_unlock(&fuse_lock);
+}
+
/*
* This function is called when a request is finished. Either a reply
* has arrived or it was interrupted (and not yet sent) or some error
@@ -166,12 +177,10 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req)
putback = atomic_dec_and_test(&req->count);
spin_unlock(&fuse_lock);
if (req->background) {
- if (req->inode)
- iput(req->inode);
- if (req->inode2)
- iput(req->inode2);
- if (req->file)
- fput(req->file);
+ down_read(&fc->sbput_sem);
+ if (fc->mounted)
+ fuse_release_background(req);
+ up_read(&fc->sbput_sem);
}
wake_up(&req->waitq);
if (req->in.h.opcode == FUSE_INIT) {
@@ -191,11 +200,39 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req)
fuse_putback_request(fc, req);
}
-static void background_request(struct fuse_req *req)
+/*
+ * Unfortunately request interruption not just solves the deadlock
+ * problem, it causes problems too. These stem from the fact, that an
+ * interrupted request is continued to be processed in userspace,
+ * while all the locks and object references (inode and file) held
+ * during the operation are released.
+ *
+ * To release the locks is exactly why there's a need to interrupt the
+ * request, so there's not a lot that can be done about this, except
+ * introduce additional locking in userspace.
+ *
+ * More important is to keep inode and file references until userspace
+ * has replied, otherwise FORGET and RELEASE could be sent while the
+ * inode/file is still used by the filesystem.
+ *
+ * For this reason the concept of "background" request is introduced.
+ * An interrupted request is backgrounded if it has been already sent
+ * to userspace. Backgrounding involves getting an extra reference to
+ * inode(s) or file used in the request, and adding the request to
+ * fc->background list. When a reply is received for a background
+ * request, the object references are released, and the request is
+ * removed from the list. If the filesystem is unmounted while there
+ * are still background requests, the list is walked and references
+ * are released as if a reply was received.
+ *
+ * There's one more use for a background request. The RELEASE message is
+ * always sent as background, since it doesn't return an error or
+ * data.
+ */
+static void background_request(struct fuse_conn *fc, struct fuse_req *req)
{
- /* Need to get hold of the inode(s) and/or file used in the
- request, so FORGET and RELEASE are not sent too early */
req->background = 1;
+ list_add(&req->bg_entry, &fc->background);
if (req->inode)
req->inode = igrab(req->inode);
if (req->inode2)
@@ -215,7 +252,8 @@ static int request_wait_answer_nonint(struct fuse_req *req)
}
/* Called with fuse_lock held. Releases, and then reacquires it. */
-static void request_wait_answer(struct fuse_req *req, int interruptible)
+static void request_wait_answer(struct fuse_conn *fc, struct fuse_req *req,
+ int interruptible)
{
int intr;
@@ -255,7 +293,7 @@ static void request_wait_answer(struct fuse_req *req, int interruptible)
list_del(&req->list);
__fuse_put_request(req);
} else if (!req->finished && req->sent)
- background_request(req);
+ background_request(fc, req);
}
static unsigned len_args(unsigned numargs, struct fuse_arg *args)
@@ -297,7 +335,7 @@ static void request_send_wait(struct fuse_conn *fc, struct fuse_req *req,
{
req->isreply = 1;
spin_lock(&fuse_lock);
- if (!fc->file)
+ if (!fc->connected)
req->out.h.error = -ENOTCONN;
else if (fc->conn_error)
req->out.h.error = -ECONNREFUSED;
@@ -307,7 +345,7 @@ static void request_send_wait(struct fuse_conn *fc, struct fuse_req *req,
after request_end() */
__fuse_get_request(req);
- request_wait_answer(req, interruptible);
+ request_wait_answer(fc, req, interruptible);
}
spin_unlock(&fuse_lock);
}
@@ -330,7 +368,7 @@ void request_send_nonint(struct fuse_conn *fc, struct fuse_req *req)
static void request_send_nowait(struct fuse_conn *fc, struct fuse_req *req)
{
spin_lock(&fuse_lock);
- if (fc->file) {
+ if (fc->connected) {
queue_request(fc, req);
spin_unlock(&fuse_lock);
} else {
@@ -348,7 +386,9 @@ void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req)
void request_send_background(struct fuse_conn *fc, struct fuse_req *req)
{
req->isreply = 1;
- background_request(req);
+ spin_lock(&fuse_lock);
+ background_request(fc, req);
+ spin_unlock(&fuse_lock);
request_send_nowait(fc, req);
}
@@ -583,7 +623,7 @@ static void request_wait(struct fuse_conn *fc)
DECLARE_WAITQUEUE(wait, current);
add_wait_queue_exclusive(&fc->waitq, &wait);
- while (fc->sb && list_empty(&fc->pending)) {
+ while (fc->mounted && list_empty(&fc->pending)) {
set_current_state(TASK_INTERRUPTIBLE);
if (signal_pending(current))
break;
@@ -622,7 +662,7 @@ static ssize_t fuse_dev_readv(struct file *file, const struct iovec *iov,
goto err_unlock;
request_wait(fc);
err = -ENODEV;
- if (!fc->sb)
+ if (!fc->mounted)
goto err_unlock;
err = -ERESTARTSYS;
if (list_empty(&fc->pending))
@@ -839,7 +879,7 @@ static int fuse_dev_release(struct inode *inode, struct file *file)
spin_lock(&fuse_lock);
fc = file->private_data;
if (fc) {
- fc->file = NULL;
+ fc->connected = 0;
end_requests(fc, &fc->pending);
end_requests(fc, &fc->processing);
fuse_release_conn(fc);