[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>

authored by Miklos Szeredi and committed by Linus Torvalds 69a53bf2 0cd5b885

+88 -6
+67 -4
fs/fuse/dev.c
··· 260 wait_event_interruptible(req->waitq, req->state == FUSE_REQ_FINISHED); 261 restore_sigs(&oldset); 262 spin_lock(&fuse_lock); 263 - if (req->state == FUSE_REQ_FINISHED) 264 return; 265 266 - req->out.h.error = -EINTR; 267 - req->interrupted = 1; 268 if (req->locked) { 269 /* This is uninterruptible sleep, because data is 270 being copied to/from the buffers of req. During ··· 772 goto err_finish; 773 774 spin_lock(&fuse_lock); 775 req = request_find(fc, oh.unique); 776 err = -EINVAL; 777 if (!req) ··· 842 return mask; 843 } 844 845 - /* Abort all requests on the given list (pending or processing) */ 846 static void end_requests(struct fuse_conn *fc, struct list_head *head) 847 { 848 while (!list_empty(head)) { ··· 856 request_end(fc, req); 857 spin_lock(&fuse_lock); 858 } 859 } 860 861 static int fuse_dev_release(struct inode *inode, struct file *file)
··· 260 wait_event_interruptible(req->waitq, req->state == FUSE_REQ_FINISHED); 261 restore_sigs(&oldset); 262 spin_lock(&fuse_lock); 263 + if (req->state == FUSE_REQ_FINISHED && !req->interrupted) 264 return; 265 266 + if (!req->interrupted) { 267 + req->out.h.error = -EINTR; 268 + req->interrupted = 1; 269 + } 270 if (req->locked) { 271 /* This is uninterruptible sleep, because data is 272 being copied to/from the buffers of req. During ··· 770 goto err_finish; 771 772 spin_lock(&fuse_lock); 773 + err = -ENOENT; 774 + if (!fc->connected) 775 + goto err_unlock; 776 + 777 req = request_find(fc, oh.unique); 778 err = -EINVAL; 779 if (!req) ··· 836 return mask; 837 } 838 839 + /* 840 + * Abort all requests on the given list (pending or processing) 841 + * 842 + * This function releases and reacquires fuse_lock 843 + */ 844 static void end_requests(struct fuse_conn *fc, struct list_head *head) 845 { 846 while (!list_empty(head)) { ··· 846 request_end(fc, req); 847 spin_lock(&fuse_lock); 848 } 849 + } 850 + 851 + /* 852 + * Abort requests under I/O 853 + * 854 + * The requests are set to interrupted and finished, and the request 855 + * waiter is woken up. This will make request_wait_answer() wait 856 + * until the request is unlocked and then return. 857 + */ 858 + static void end_io_requests(struct fuse_conn *fc) 859 + { 860 + while (!list_empty(&fc->io)) { 861 + struct fuse_req *req; 862 + req = list_entry(fc->io.next, struct fuse_req, list); 863 + req->interrupted = 1; 864 + req->out.h.error = -ECONNABORTED; 865 + req->state = FUSE_REQ_FINISHED; 866 + list_del_init(&req->list); 867 + wake_up(&req->waitq); 868 + } 869 + } 870 + 871 + /* 872 + * Abort all requests. 873 + * 874 + * Emergency exit in case of a malicious or accidental deadlock, or 875 + * just a hung filesystem. 876 + * 877 + * The same effect is usually achievable through killing the 878 + * filesystem daemon and all users of the filesystem. The exception 879 + * is the combination of an asynchronous request and the tricky 880 + * deadlock (see Documentation/filesystems/fuse.txt). 881 + * 882 + * During the aborting, progression of requests from the pending and 883 + * processing lists onto the io list, and progression of new requests 884 + * onto the pending list is prevented by req->connected being false. 885 + * 886 + * Progression of requests under I/O to the processing list is 887 + * prevented by the req->interrupted flag being true for these 888 + * requests. For this reason requests on the io list must be aborted 889 + * first. 890 + */ 891 + void fuse_abort_conn(struct fuse_conn *fc) 892 + { 893 + spin_lock(&fuse_lock); 894 + if (fc->connected) { 895 + fc->connected = 0; 896 + end_io_requests(fc); 897 + end_requests(fc, &fc->pending); 898 + end_requests(fc, &fc->processing); 899 + wake_up_all(&fc->waitq); 900 + } 901 + spin_unlock(&fuse_lock); 902 } 903 904 static int fuse_dev_release(struct inode *inode, struct file *file)
+5 -2
fs/fuse/fuse_i.h
··· 246 /** Mount is active */ 247 unsigned mounted : 1; 248 249 - /** Connection established, cleared on umount and device 250 - release */ 251 unsigned connected : 1; 252 253 /** Connection failed (version mismatch) */ ··· 462 * Release inodes and file associated with background request 463 */ 464 void fuse_release_background(struct fuse_req *req); 465 466 /** 467 * Get the attributes of a file
··· 246 /** Mount is active */ 247 unsigned mounted : 1; 248 249 + /** Connection established, cleared on umount, connection 250 + abort and device release */ 251 unsigned connected : 1; 252 253 /** Connection failed (version mismatch) */ ··· 462 * Release inodes and file associated with background request 463 */ 464 void fuse_release_background(struct fuse_req *req); 465 + 466 + /* Abort all requests */ 467 + void fuse_abort_conn(struct fuse_conn *fc); 468 469 /** 470 * Get the attributes of a file
+16
fs/fuse/inode.c
··· 196 return inode; 197 } 198 199 static void fuse_put_super(struct super_block *sb) 200 { 201 struct fuse_conn *fc = get_fuse_conn_super(sb); ··· 459 .read_inode = fuse_read_inode, 460 .clear_inode = fuse_clear_inode, 461 .put_super = fuse_put_super, 462 .statfs = fuse_statfs, 463 .show_options = fuse_show_options, 464 }; ··· 566 return sprintf(page, "%i\n", atomic_read(&fc->num_waiting)); 567 } 568 569 static struct fuse_conn_attr fuse_conn_waiting = 570 __ATTR(waiting, 0400, fuse_conn_waiting_show, NULL); 571 572 static struct attribute *fuse_conn_attrs[] = { 573 &fuse_conn_waiting.attr, 574 NULL, 575 }; 576
··· 196 return inode; 197 } 198 199 + static void fuse_umount_begin(struct super_block *sb) 200 + { 201 + fuse_abort_conn(get_fuse_conn_super(sb)); 202 + } 203 + 204 static void fuse_put_super(struct super_block *sb) 205 { 206 struct fuse_conn *fc = get_fuse_conn_super(sb); ··· 454 .read_inode = fuse_read_inode, 455 .clear_inode = fuse_clear_inode, 456 .put_super = fuse_put_super, 457 + .umount_begin = fuse_umount_begin, 458 .statfs = fuse_statfs, 459 .show_options = fuse_show_options, 460 }; ··· 560 return sprintf(page, "%i\n", atomic_read(&fc->num_waiting)); 561 } 562 563 + static ssize_t fuse_conn_abort_store(struct fuse_conn *fc, const char *page, 564 + size_t count) 565 + { 566 + fuse_abort_conn(fc); 567 + return count; 568 + } 569 + 570 static struct fuse_conn_attr fuse_conn_waiting = 571 __ATTR(waiting, 0400, fuse_conn_waiting_show, NULL); 572 + static struct fuse_conn_attr fuse_conn_abort = 573 + __ATTR(abort, 0600, NULL, fuse_conn_abort_store); 574 575 static struct attribute *fuse_conn_attrs[] = { 576 &fuse_conn_waiting.attr, 577 + &fuse_conn_abort.attr, 578 NULL, 579 }; 580