diff options
author | Miklos Szeredi <mszeredi@suse.cz> | 2011-02-25 08:44:58 -0500 |
---|---|---|
committer | Miklos Szeredi <mszeredi@suse.cz> | 2011-02-25 08:44:58 -0500 |
commit | 5a18ec176c934ca1bc9dc61580a5e0e90a9b5733 (patch) | |
tree | 6995509f59166fff90ce240ce72e3858f61ac101 /fs/fuse/file.c | |
parent | 4662db446190ddef8fbab024f72dee77dd04b8f2 (diff) |
fuse: fix hang of single threaded fuseblk filesystem
Single threaded NTFS-3G could get stuck if a delayed RELEASE reply
triggered a DESTROY request via path_put().
Fix this by
a) making RELEASE requests synchronous, whenever possible, on fuseblk
filesystems
b) if not possible (triggered by an asynchronous read/write) then do
the path_put() in a separate thread with schedule_work().
Reported-by: Oliver Neukum <oneukum@suse.de>
Cc: stable@kernel.org
Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
Diffstat (limited to 'fs/fuse/file.c')
-rw-r--r-- | fs/fuse/file.c | 52 |
1 files changed, 45 insertions, 7 deletions
diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 95da1bc1c826..9e0832dbb1e3 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c | |||
@@ -86,18 +86,52 @@ struct fuse_file *fuse_file_get(struct fuse_file *ff) | |||
86 | return ff; | 86 | return ff; |
87 | } | 87 | } |
88 | 88 | ||
89 | static void fuse_release_async(struct work_struct *work) | ||
90 | { | ||
91 | struct fuse_req *req; | ||
92 | struct fuse_conn *fc; | ||
93 | struct path path; | ||
94 | |||
95 | req = container_of(work, struct fuse_req, misc.release.work); | ||
96 | path = req->misc.release.path; | ||
97 | fc = get_fuse_conn(path.dentry->d_inode); | ||
98 | |||
99 | fuse_put_request(fc, req); | ||
100 | path_put(&path); | ||
101 | } | ||
102 | |||
89 | static void fuse_release_end(struct fuse_conn *fc, struct fuse_req *req) | 103 | static void fuse_release_end(struct fuse_conn *fc, struct fuse_req *req) |
90 | { | 104 | { |
91 | path_put(&req->misc.release.path); | 105 | if (fc->destroy_req) { |
106 | /* | ||
107 | * If this is a fuseblk mount, then it's possible that | ||
108 | * releasing the path will result in releasing the | ||
109 | * super block and sending the DESTROY request. If | ||
110 | * the server is single threaded, this would hang. | ||
111 | * For this reason do the path_put() in a separate | ||
112 | * thread. | ||
113 | */ | ||
114 | atomic_inc(&req->count); | ||
115 | INIT_WORK(&req->misc.release.work, fuse_release_async); | ||
116 | schedule_work(&req->misc.release.work); | ||
117 | } else { | ||
118 | path_put(&req->misc.release.path); | ||
119 | } | ||
92 | } | 120 | } |
93 | 121 | ||
94 | static void fuse_file_put(struct fuse_file *ff) | 122 | static void fuse_file_put(struct fuse_file *ff, bool sync) |
95 | { | 123 | { |
96 | if (atomic_dec_and_test(&ff->count)) { | 124 | if (atomic_dec_and_test(&ff->count)) { |
97 | struct fuse_req *req = ff->reserved_req; | 125 | struct fuse_req *req = ff->reserved_req; |
98 | 126 | ||
99 | req->end = fuse_release_end; | 127 | if (sync) { |
100 | fuse_request_send_background(ff->fc, req); | 128 | fuse_request_send(ff->fc, req); |
129 | path_put(&req->misc.release.path); | ||
130 | fuse_put_request(ff->fc, req); | ||
131 | } else { | ||
132 | req->end = fuse_release_end; | ||
133 | fuse_request_send_background(ff->fc, req); | ||
134 | } | ||
101 | kfree(ff); | 135 | kfree(ff); |
102 | } | 136 | } |
103 | } | 137 | } |
@@ -219,8 +253,12 @@ void fuse_release_common(struct file *file, int opcode) | |||
219 | * Normally this will send the RELEASE request, however if | 253 | * Normally this will send the RELEASE request, however if |
220 | * some asynchronous READ or WRITE requests are outstanding, | 254 | * some asynchronous READ or WRITE requests are outstanding, |
221 | * the sending will be delayed. | 255 | * the sending will be delayed. |
256 | * | ||
257 | * Make the release synchronous if this is a fuseblk mount, | ||
258 | * synchronous RELEASE is allowed (and desirable) in this case | ||
259 | * because the server can be trusted not to screw up. | ||
222 | */ | 260 | */ |
223 | fuse_file_put(ff); | 261 | fuse_file_put(ff, ff->fc->destroy_req != NULL); |
224 | } | 262 | } |
225 | 263 | ||
226 | static int fuse_open(struct inode *inode, struct file *file) | 264 | static int fuse_open(struct inode *inode, struct file *file) |
@@ -558,7 +596,7 @@ static void fuse_readpages_end(struct fuse_conn *fc, struct fuse_req *req) | |||
558 | page_cache_release(page); | 596 | page_cache_release(page); |
559 | } | 597 | } |
560 | if (req->ff) | 598 | if (req->ff) |
561 | fuse_file_put(req->ff); | 599 | fuse_file_put(req->ff, false); |
562 | } | 600 | } |
563 | 601 | ||
564 | static void fuse_send_readpages(struct fuse_req *req, struct file *file) | 602 | static void fuse_send_readpages(struct fuse_req *req, struct file *file) |
@@ -1137,7 +1175,7 @@ static ssize_t fuse_direct_write(struct file *file, const char __user *buf, | |||
1137 | static void fuse_writepage_free(struct fuse_conn *fc, struct fuse_req *req) | 1175 | static void fuse_writepage_free(struct fuse_conn *fc, struct fuse_req *req) |
1138 | { | 1176 | { |
1139 | __free_page(req->pages[0]); | 1177 | __free_page(req->pages[0]); |
1140 | fuse_file_put(req->ff); | 1178 | fuse_file_put(req->ff, false); |
1141 | } | 1179 | } |
1142 | 1180 | ||
1143 | static void fuse_writepage_finish(struct fuse_conn *fc, struct fuse_req *req) | 1181 | static void fuse_writepage_finish(struct fuse_conn *fc, struct fuse_req *req) |