diff options
author | Miklos Szeredi <miklos@szeredi.hu> | 2006-01-17 01:14:41 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-01-17 02:15:30 -0500 |
commit | 69a53bf267fa58b89aa659d121dfe38436562a30 (patch) | |
tree | 36276fdbf7bfdc787708e8d5b2d4b79a0b5a4c56 | |
parent | 0cd5b88553acf0611474dbaf8e43770eed268060 (diff) |
[PATCH] fuse: add connection aborting
Add ability to abort a filesystem connection.
With the introduction of asynchronous reads, the ability to interrupt any
request is not enough to dissolve deadlocks, since now waiting for the request
completion (page unlocked) is independent of the actual request, so in a
deadlock all threads will be uninterruptible.
The solution is to make it possible to abort all requests, even those
currently undergoing I/O to/from userspace. The natural interface for this is
'mount -f mountpoint', but that only works as long as the filesystem is
attached. So also add an 'abort' attribute to the sysfs view of the
connection.
Signed-off-by: Miklos Szeredi <miklos@szeredi.hu>
Cc: Greg KH <greg@kroah.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
-rw-r--r-- | fs/fuse/dev.c | 71 | ||||
-rw-r--r-- | fs/fuse/fuse_i.h | 7 | ||||
-rw-r--r-- | fs/fuse/inode.c | 16 |
3 files changed, 88 insertions, 6 deletions
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index c72e44b58d09..60c222517ccd 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c | |||
@@ -260,11 +260,13 @@ static void request_wait_answer(struct fuse_conn *fc, struct fuse_req *req) | |||
260 | wait_event_interruptible(req->waitq, req->state == FUSE_REQ_FINISHED); | 260 | wait_event_interruptible(req->waitq, req->state == FUSE_REQ_FINISHED); |
261 | restore_sigs(&oldset); | 261 | restore_sigs(&oldset); |
262 | spin_lock(&fuse_lock); | 262 | spin_lock(&fuse_lock); |
263 | if (req->state == FUSE_REQ_FINISHED) | 263 | if (req->state == FUSE_REQ_FINISHED && !req->interrupted) |
264 | return; | 264 | return; |
265 | 265 | ||
266 | req->out.h.error = -EINTR; | 266 | if (!req->interrupted) { |
267 | req->interrupted = 1; | 267 | req->out.h.error = -EINTR; |
268 | req->interrupted = 1; | ||
269 | } | ||
268 | if (req->locked) { | 270 | if (req->locked) { |
269 | /* This is uninterruptible sleep, because data is | 271 | /* This is uninterruptible sleep, because data is |
270 | being copied to/from the buffers of req. During | 272 | being copied to/from the buffers of req. During |
@@ -770,6 +772,10 @@ static ssize_t fuse_dev_writev(struct file *file, const struct iovec *iov, | |||
770 | goto err_finish; | 772 | goto err_finish; |
771 | 773 | ||
772 | spin_lock(&fuse_lock); | 774 | spin_lock(&fuse_lock); |
775 | err = -ENOENT; | ||
776 | if (!fc->connected) | ||
777 | goto err_unlock; | ||
778 | |||
773 | req = request_find(fc, oh.unique); | 779 | req = request_find(fc, oh.unique); |
774 | err = -EINVAL; | 780 | err = -EINVAL; |
775 | if (!req) | 781 | if (!req) |
@@ -836,7 +842,11 @@ static unsigned fuse_dev_poll(struct file *file, poll_table *wait) | |||
836 | return mask; | 842 | return mask; |
837 | } | 843 | } |
838 | 844 | ||
839 | /* Abort all requests on the given list (pending or processing) */ | 845 | /* |
846 | * Abort all requests on the given list (pending or processing) | ||
847 | * | ||
848 | * This function releases and reacquires fuse_lock | ||
849 | */ | ||
840 | static void end_requests(struct fuse_conn *fc, struct list_head *head) | 850 | static void end_requests(struct fuse_conn *fc, struct list_head *head) |
841 | { | 851 | { |
842 | while (!list_empty(head)) { | 852 | while (!list_empty(head)) { |
@@ -848,6 +858,59 @@ static void end_requests(struct fuse_conn *fc, struct list_head *head) | |||
848 | } | 858 | } |
849 | } | 859 | } |
850 | 860 | ||
861 | /* | ||
862 | * Abort requests under I/O | ||
863 | * | ||
864 | * The requests are set to interrupted and finished, and the request | ||
865 | * waiter is woken up. This will make request_wait_answer() wait | ||
866 | * until the request is unlocked and then return. | ||
867 | */ | ||
868 | static void end_io_requests(struct fuse_conn *fc) | ||
869 | { | ||
870 | while (!list_empty(&fc->io)) { | ||
871 | struct fuse_req *req; | ||
872 | req = list_entry(fc->io.next, struct fuse_req, list); | ||
873 | req->interrupted = 1; | ||
874 | req->out.h.error = -ECONNABORTED; | ||
875 | req->state = FUSE_REQ_FINISHED; | ||
876 | list_del_init(&req->list); | ||
877 | wake_up(&req->waitq); | ||
878 | } | ||
879 | } | ||
880 | |||
881 | /* | ||
882 | * Abort all requests. | ||
883 | * | ||
884 | * Emergency exit in case of a malicious or accidental deadlock, or | ||
885 | * just a hung filesystem. | ||
886 | * | ||
887 | * The same effect is usually achievable through killing the | ||
888 | * filesystem daemon and all users of the filesystem. The exception | ||
889 | * is the combination of an asynchronous request and the tricky | ||
890 | * deadlock (see Documentation/filesystems/fuse.txt). | ||
891 | * | ||
892 | * During the aborting, progression of requests from the pending and | ||
893 | * processing lists onto the io list, and progression of new requests | ||
894 | * onto the pending list is prevented by req->connected being false. | ||
895 | * | ||
896 | * Progression of requests under I/O to the processing list is | ||
897 | * prevented by the req->interrupted flag being true for these | ||
898 | * requests. For this reason requests on the io list must be aborted | ||
899 | * first. | ||
900 | */ | ||
901 | void fuse_abort_conn(struct fuse_conn *fc) | ||
902 | { | ||
903 | spin_lock(&fuse_lock); | ||
904 | if (fc->connected) { | ||
905 | fc->connected = 0; | ||
906 | end_io_requests(fc); | ||
907 | end_requests(fc, &fc->pending); | ||
908 | end_requests(fc, &fc->processing); | ||
909 | wake_up_all(&fc->waitq); | ||
910 | } | ||
911 | spin_unlock(&fuse_lock); | ||
912 | } | ||
913 | |||
851 | static int fuse_dev_release(struct inode *inode, struct file *file) | 914 | static int fuse_dev_release(struct inode *inode, struct file *file) |
852 | { | 915 | { |
853 | struct fuse_conn *fc; | 916 | struct fuse_conn *fc; |
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index bcb453f68111..e6381db41df9 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h | |||
@@ -246,8 +246,8 @@ struct fuse_conn { | |||
246 | /** Mount is active */ | 246 | /** Mount is active */ |
247 | unsigned mounted : 1; | 247 | unsigned mounted : 1; |
248 | 248 | ||
249 | /** Connection established, cleared on umount and device | 249 | /** Connection established, cleared on umount, connection |
250 | release */ | 250 | abort and device release */ |
251 | unsigned connected : 1; | 251 | unsigned connected : 1; |
252 | 252 | ||
253 | /** Connection failed (version mismatch) */ | 253 | /** Connection failed (version mismatch) */ |
@@ -463,6 +463,9 @@ void request_send_background(struct fuse_conn *fc, struct fuse_req *req); | |||
463 | */ | 463 | */ |
464 | void fuse_release_background(struct fuse_req *req); | 464 | void fuse_release_background(struct fuse_req *req); |
465 | 465 | ||
466 | /* Abort all requests */ | ||
467 | void fuse_abort_conn(struct fuse_conn *fc); | ||
468 | |||
466 | /** | 469 | /** |
467 | * Get the attributes of a file | 470 | * Get the attributes of a file |
468 | */ | 471 | */ |
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 182235923cdd..d359d8de22a4 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c | |||
@@ -196,6 +196,11 @@ struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid, | |||
196 | return inode; | 196 | return inode; |
197 | } | 197 | } |
198 | 198 | ||
199 | static void fuse_umount_begin(struct super_block *sb) | ||
200 | { | ||
201 | fuse_abort_conn(get_fuse_conn_super(sb)); | ||
202 | } | ||
203 | |||
199 | static void fuse_put_super(struct super_block *sb) | 204 | static void fuse_put_super(struct super_block *sb) |
200 | { | 205 | { |
201 | struct fuse_conn *fc = get_fuse_conn_super(sb); | 206 | struct fuse_conn *fc = get_fuse_conn_super(sb); |
@@ -454,6 +459,7 @@ static struct super_operations fuse_super_operations = { | |||
454 | .read_inode = fuse_read_inode, | 459 | .read_inode = fuse_read_inode, |
455 | .clear_inode = fuse_clear_inode, | 460 | .clear_inode = fuse_clear_inode, |
456 | .put_super = fuse_put_super, | 461 | .put_super = fuse_put_super, |
462 | .umount_begin = fuse_umount_begin, | ||
457 | .statfs = fuse_statfs, | 463 | .statfs = fuse_statfs, |
458 | .show_options = fuse_show_options, | 464 | .show_options = fuse_show_options, |
459 | }; | 465 | }; |
@@ -560,11 +566,21 @@ static ssize_t fuse_conn_waiting_show(struct fuse_conn *fc, char *page) | |||
560 | return sprintf(page, "%i\n", atomic_read(&fc->num_waiting)); | 566 | return sprintf(page, "%i\n", atomic_read(&fc->num_waiting)); |
561 | } | 567 | } |
562 | 568 | ||
569 | static ssize_t fuse_conn_abort_store(struct fuse_conn *fc, const char *page, | ||
570 | size_t count) | ||
571 | { | ||
572 | fuse_abort_conn(fc); | ||
573 | return count; | ||
574 | } | ||
575 | |||
563 | static struct fuse_conn_attr fuse_conn_waiting = | 576 | static struct fuse_conn_attr fuse_conn_waiting = |
564 | __ATTR(waiting, 0400, fuse_conn_waiting_show, NULL); | 577 | __ATTR(waiting, 0400, fuse_conn_waiting_show, NULL); |
578 | static struct fuse_conn_attr fuse_conn_abort = | ||
579 | __ATTR(abort, 0600, NULL, fuse_conn_abort_store); | ||
565 | 580 | ||
566 | static struct attribute *fuse_conn_attrs[] = { | 581 | static struct attribute *fuse_conn_attrs[] = { |
567 | &fuse_conn_waiting.attr, | 582 | &fuse_conn_waiting.attr, |
583 | &fuse_conn_abort.attr, | ||
568 | NULL, | 584 | NULL, |
569 | }; | 585 | }; |
570 | 586 | ||