Commit 7128ec2a authored by Miklos Szeredi's avatar Miklos Szeredi Committed by Linus Torvalds

[PATCH] fuse: fix request_end() vs fuse_reset_request() race

The last fix for this function in fact opened up a much more often
triggering race.

It was uncommented tricky code, that was buggy.  Add comment, make it less
tricky and fix bug.
Signed-off-by: default avatarMiklos Szeredi <miklos@szeredi.hu>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent e22bec26
...@@ -120,9 +120,9 @@ struct fuse_req *fuse_get_request(struct fuse_conn *fc) ...@@ -120,9 +120,9 @@ struct fuse_req *fuse_get_request(struct fuse_conn *fc)
return do_get_request(fc); return do_get_request(fc);
} }
/* Must be called with fuse_lock held */
static void fuse_putback_request(struct fuse_conn *fc, struct fuse_req *req) static void fuse_putback_request(struct fuse_conn *fc, struct fuse_req *req)
{ {
spin_lock(&fuse_lock);
if (req->preallocated) { if (req->preallocated) {
atomic_dec(&fc->num_waiting); atomic_dec(&fc->num_waiting);
list_add(&req->list, &fc->unused_list); list_add(&req->list, &fc->unused_list);
...@@ -134,10 +134,18 @@ static void fuse_putback_request(struct fuse_conn *fc, struct fuse_req *req) ...@@ -134,10 +134,18 @@ static void fuse_putback_request(struct fuse_conn *fc, struct fuse_req *req)
fc->outstanding_debt--; fc->outstanding_debt--;
else else
up(&fc->outstanding_sem); up(&fc->outstanding_sem);
spin_unlock(&fuse_lock);
} }
void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req) void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req)
{
if (atomic_dec_and_test(&req->count)) {
spin_lock(&fuse_lock);
fuse_putback_request(fc, req);
spin_unlock(&fuse_lock);
}
}
static void fuse_put_request_locked(struct fuse_conn *fc, struct fuse_req *req)
{ {
if (atomic_dec_and_test(&req->count)) if (atomic_dec_and_test(&req->count))
fuse_putback_request(fc, req); fuse_putback_request(fc, req);
...@@ -163,26 +171,36 @@ void fuse_release_background(struct fuse_req *req) ...@@ -163,26 +171,36 @@ void fuse_release_background(struct fuse_req *req)
* still waiting), the 'end' callback is called if given, else the * still waiting), the 'end' callback is called if given, else the
* reference to the request is released * reference to the request is released
* *
* Releasing extra reference for foreground requests must be done
* within the same locked region as setting state to finished. This
* is because fuse_reset_request() may be called after request is
* finished and it must be the sole possessor. If request is
* interrupted and put in the background, it will return with an error
* and hence never be reset and reused.
*
* Called with fuse_lock, unlocks it * Called with fuse_lock, unlocks it
*/ */
static void request_end(struct fuse_conn *fc, struct fuse_req *req) static void request_end(struct fuse_conn *fc, struct fuse_req *req)
{ {
void (*end) (struct fuse_conn *, struct fuse_req *) = req->end;
req->end = NULL;
list_del(&req->list); list_del(&req->list);
req->state = FUSE_REQ_FINISHED; req->state = FUSE_REQ_FINISHED;
if (!req->background) {
wake_up(&req->waitq);
fuse_put_request_locked(fc, req);
spin_unlock(&fuse_lock);
} else {
void (*end) (struct fuse_conn *, struct fuse_req *) = req->end;
req->end = NULL;
spin_unlock(&fuse_lock); spin_unlock(&fuse_lock);
if (req->background) {
down_read(&fc->sbput_sem); down_read(&fc->sbput_sem);
if (fc->mounted) if (fc->mounted)
fuse_release_background(req); fuse_release_background(req);
up_read(&fc->sbput_sem); up_read(&fc->sbput_sem);
}
wake_up(&req->waitq);
if (end) if (end)
end(fc, req); end(fc, req);
else else
fuse_put_request(fc, req); fuse_put_request(fc, req);
}
} }
/* /*
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment