diff options
Diffstat (limited to 'fs/fuse')
-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 | ||