From: Miklos Szeredi If background requests (*) were pending at umount time, the inodes stored in the request weren't released, resulting in busy inodes after unmount and possibly Oopsen. This patch fixes this bug by storing background requests on a separate list. In fuse_put_super() the list is walked and the inodes belonging to the background requests are released. In addition sending FORGET messages from fuse_clear_inode() is inhibited if unmount has started (MS_ACTIVE flag not set). Releasing inodes in fuse_super() results in a race with request_end() doing the same. This is resolved with a per-mount RW semaphore which is acquired for read in request_end() and for write in fuse_put_super(). (*) requests for which the original requester thread isn't waiting any more Signed-off-by: Miklos Szeredi Signed-off-by: Andrew Morton --- fs/fuse/dev.c | 33 ++++++++++++++++++++++----------- fs/fuse/fuse_i.h | 15 +++++++++++++++ fs/fuse/inode.c | 10 +++++++++- 3 files changed, 46 insertions(+), 12 deletions(-) diff -puN fs/fuse/dev.c~fuse-mount-options-fix fs/fuse/dev.c --- 25/fs/fuse/dev.c~fuse-mount-options-fix 2005-05-10 02:22:00.000000000 -0700 +++ 25-akpm/fs/fuse/dev.c 2005-05-10 02:22:00.000000000 -0700 @@ -146,6 +146,17 @@ void fuse_put_request(struct fuse_conn * fuse_putback_request(fc, req); } +void fuse_release_background(struct fuse_req *req) +{ + if (req->inode) + iput(req->inode); + if (req->inode2) + iput(req->inode2); + if (req->file) + fput(req->file); + list_del(&req->bg_entry); +} + /* * 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 @@ -164,12 +175,10 @@ static void request_end(struct fuse_conn 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->sb) + fuse_release_background(req); + up_read(&fc->sbput_sem); } wake_up(&req->waitq); if (req->in.h.opcode == FUSE_INIT) { @@ -189,11 +198,12 @@ static void request_end(struct fuse_conn fuse_putback_request(fc, req); } -static void background_request(struct fuse_req *req) +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) @@ -213,7 +223,8 @@ static int request_wait_answer_nonint(st } /* 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; @@ -253,7 +264,7 @@ static void request_wait_answer(struct f 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) @@ -305,7 +316,7 @@ static void request_send_wait(struct fus after request_end() */ __fuse_get_request(req); - request_wait_answer(req, interruptible); + request_wait_answer(fc, req, interruptible); } spin_unlock(&fuse_lock); } @@ -346,7 +357,7 @@ void request_send_noreply(struct fuse_co void request_send_background(struct fuse_conn *fc, struct fuse_req *req) { req->isreply = 1; - background_request(req); + background_request(fc, req); request_send_nowait(fc, req); } diff -puN fs/fuse/fuse_i.h~fuse-mount-options-fix fs/fuse/fuse_i.h --- 25/fs/fuse/fuse_i.h~fuse-mount-options-fix 2005-05-10 02:22:00.000000000 -0700 +++ 25-akpm/fs/fuse/fuse_i.h 2005-05-10 02:22:00.000000000 -0700 @@ -126,6 +126,9 @@ struct fuse_req { lists in fuse_conn */ struct list_head list; + /** Entry on the background list */ + struct list_head bg_entry; + /** refcount */ atomic_t count; @@ -214,6 +217,10 @@ struct fuse_conn { /** The list of requests being processed */ struct list_head processing; + /** Requests put in the background (RELEASE or any other + interrupted request) */ + struct list_head background; + /** Controls the maximum number of outstanding requests */ struct semaphore outstanding_sem; @@ -221,6 +228,9 @@ struct fuse_conn { outstanding_sem would go negative */ unsigned outstanding_debt; + /** RW semaphore for exclusion with fuse_put_super() */ + struct rw_semaphore sbput_sem; + /** The list of unused requests */ struct list_head unused_list; @@ -392,6 +402,11 @@ void request_send_noreply(struct fuse_co void request_send_background(struct fuse_conn *fc, struct fuse_req *req); /** + * Release inodes and file assiciated with background request + */ +void fuse_release_background(struct fuse_req *req); + +/** * Get the attributes of a file */ int fuse_do_getattr(struct inode *inode); diff -puN fs/fuse/inode.c~fuse-mount-options-fix fs/fuse/inode.c --- 25/fs/fuse/inode.c~fuse-mount-options-fix 2005-05-10 02:22:00.000000000 -0700 +++ 25-akpm/fs/fuse/inode.c 2005-05-10 02:22:00.000000000 -0700 @@ -85,7 +85,7 @@ void fuse_send_forget(struct fuse_conn * static void fuse_clear_inode(struct inode *inode) { struct fuse_conn *fc = get_fuse_conn(inode); - if (fc) { + if (fc && (inode->i_sb->s_flags & MS_ACTIVE)) { struct fuse_inode *fi = get_fuse_inode(inode); fuse_send_forget(fc, fi->forget_req, fi->nodeid, fi->nlookup); fi->forget_req = NULL; @@ -190,12 +190,18 @@ static void fuse_put_super(struct super_ { struct fuse_conn *fc = get_fuse_conn_super(sb); + down_write(&fc->sbput_sem); + while (!list_empty(&fc->background)) + fuse_release_background(list_entry(fc->background.next, + struct fuse_req, bg_entry)); + spin_lock(&fuse_lock); fc->sb = NULL; fc->user_id = 0; fc->flags = 0; /* Flush all readers on this fs */ wake_up_all(&fc->waitq); + up_write(&fc->sbput_sem); fuse_release_conn(fc); *get_fuse_conn_super_p(sb) = NULL; spin_unlock(&fuse_lock); @@ -369,7 +375,9 @@ static struct fuse_conn *new_conn(void) INIT_LIST_HEAD(&fc->pending); INIT_LIST_HEAD(&fc->processing); INIT_LIST_HEAD(&fc->unused_list); + INIT_LIST_HEAD(&fc->background); sema_init(&fc->outstanding_sem, 0); + init_rwsem(&fc->sbput_sem); for (i = 0; i < FUSE_MAX_OUTSTANDING; i++) { struct fuse_req *req = fuse_request_alloc(); if (!req) { _