|
@@ -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);
|