aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSzymon Lukasz <noh4hss@gmail.com>2017-11-09 15:23:35 -0500
committerMiklos Szeredi <mszeredi@redhat.com>2018-03-20 12:11:44 -0400
commit3b7008b226f3de811d4ac34238e9cf670f7c9fe7 (patch)
tree5f12afe06b3761c54191e37b03377d8da25b8331
parentdf0e91d488276086bc07da2e389986cae0048c37 (diff)
fuse: return -ECONNABORTED on /dev/fuse read after abort
Currently the userspace has no way of knowing whether the fuse connection ended because of umount or abort via sysfs. It makes it hard for filesystems to free the mountpoint after abort without worrying about removing some new mount. The patch fixes it by returning different errors when userspace reads from /dev/fuse (-ENODEV for umount and -ECONNABORTED for abort). Add a new capability flag FUSE_ABORT_ERROR. If set and the connection is gone because of sysfs abort, reading from the device will return -ECONNABORTED. Signed-off-by: Szymon Lukasz <noh4hss@gmail.com> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
-rw-r--r--fs/fuse/control.c2
-rw-r--r--fs/fuse/cuse.c4
-rw-r--r--fs/fuse/dev.c12
-rw-r--r--fs/fuse/fuse_i.h8
-rw-r--r--fs/fuse/inode.c9
-rw-r--r--include/uapi/linux/fuse.h7
6 files changed, 29 insertions, 13 deletions
diff --git a/fs/fuse/control.c b/fs/fuse/control.c
index b9ea99c5b5b3..78fb7a07c5ca 100644
--- a/fs/fuse/control.c
+++ b/fs/fuse/control.c
@@ -35,7 +35,7 @@ static ssize_t fuse_conn_abort_write(struct file *file, const char __user *buf,
35{ 35{
36 struct fuse_conn *fc = fuse_ctl_file_conn_get(file); 36 struct fuse_conn *fc = fuse_ctl_file_conn_get(file);
37 if (fc) { 37 if (fc) {
38 fuse_abort_conn(fc); 38 fuse_abort_conn(fc, true);
39 fuse_conn_put(fc); 39 fuse_conn_put(fc);
40 } 40 }
41 return count; 41 return count;
diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c
index e9e97803442a..31d33b69957f 100644
--- a/fs/fuse/cuse.c
+++ b/fs/fuse/cuse.c
@@ -406,7 +406,7 @@ err_unlock:
406err_region: 406err_region:
407 unregister_chrdev_region(devt, 1); 407 unregister_chrdev_region(devt, 1);
408err: 408err:
409 fuse_abort_conn(fc); 409 fuse_abort_conn(fc, false);
410 goto out; 410 goto out;
411} 411}
412 412
@@ -581,7 +581,7 @@ static ssize_t cuse_class_abort_store(struct device *dev,
581{ 581{
582 struct cuse_conn *cc = dev_get_drvdata(dev); 582 struct cuse_conn *cc = dev_get_drvdata(dev);
583 583
584 fuse_abort_conn(&cc->fc); 584 fuse_abort_conn(&cc->fc, false);
585 return count; 585 return count;
586} 586}
587static DEVICE_ATTR(abort, 0200, NULL, cuse_class_abort_store); 587static DEVICE_ATTR(abort, 0200, NULL, cuse_class_abort_store);
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 5d06384c2cae..78b6293bd04d 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -1234,9 +1234,10 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file,
1234 if (err) 1234 if (err)
1235 goto err_unlock; 1235 goto err_unlock;
1236 1236
1237 err = -ENODEV; 1237 if (!fiq->connected) {
1238 if (!fiq->connected) 1238 err = (fc->aborted && fc->abort_err) ? -ECONNABORTED : -ENODEV;
1239 goto err_unlock; 1239 goto err_unlock;
1240 }
1240 1241
1241 if (!list_empty(&fiq->interrupts)) { 1242 if (!list_empty(&fiq->interrupts)) {
1242 req = list_entry(fiq->interrupts.next, struct fuse_req, 1243 req = list_entry(fiq->interrupts.next, struct fuse_req,
@@ -1287,7 +1288,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file,
1287 spin_lock(&fpq->lock); 1288 spin_lock(&fpq->lock);
1288 clear_bit(FR_LOCKED, &req->flags); 1289 clear_bit(FR_LOCKED, &req->flags);
1289 if (!fpq->connected) { 1290 if (!fpq->connected) {
1290 err = -ENODEV; 1291 err = (fc->aborted && fc->abort_err) ? -ECONNABORTED : -ENODEV;
1291 goto out_end; 1292 goto out_end;
1292 } 1293 }
1293 if (err) { 1294 if (err) {
@@ -2076,7 +2077,7 @@ static void end_polls(struct fuse_conn *fc)
2076 * is OK, the request will in that case be removed from the list before we touch 2077 * is OK, the request will in that case be removed from the list before we touch
2077 * it. 2078 * it.
2078 */ 2079 */
2079void fuse_abort_conn(struct fuse_conn *fc) 2080void fuse_abort_conn(struct fuse_conn *fc, bool is_abort)
2080{ 2081{
2081 struct fuse_iqueue *fiq = &fc->iq; 2082 struct fuse_iqueue *fiq = &fc->iq;
2082 2083
@@ -2089,6 +2090,7 @@ void fuse_abort_conn(struct fuse_conn *fc)
2089 2090
2090 fc->connected = 0; 2091 fc->connected = 0;
2091 fc->blocked = 0; 2092 fc->blocked = 0;
2093 fc->aborted = is_abort;
2092 fuse_set_initialized(fc); 2094 fuse_set_initialized(fc);
2093 list_for_each_entry(fud, &fc->devices, entry) { 2095 list_for_each_entry(fud, &fc->devices, entry) {
2094 struct fuse_pqueue *fpq = &fud->pq; 2096 struct fuse_pqueue *fpq = &fud->pq;
@@ -2151,7 +2153,7 @@ int fuse_dev_release(struct inode *inode, struct file *file)
2151 /* Are we the last open device? */ 2153 /* Are we the last open device? */
2152 if (atomic_dec_and_test(&fc->dev_count)) { 2154 if (atomic_dec_and_test(&fc->dev_count)) {
2153 WARN_ON(fc->iq.fasync != NULL); 2155 WARN_ON(fc->iq.fasync != NULL);
2154 fuse_abort_conn(fc); 2156 fuse_abort_conn(fc, false);
2155 } 2157 }
2156 fuse_dev_free(fud); 2158 fuse_dev_free(fud);
2157 } 2159 }
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index c4c093bbf456..7d2e7deea64b 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -515,6 +515,9 @@ struct fuse_conn {
515 abort and device release */ 515 abort and device release */
516 unsigned connected; 516 unsigned connected;
517 517
518 /** Connection aborted via sysfs */
519 bool aborted;
520
518 /** Connection failed (version mismatch). Cannot race with 521 /** Connection failed (version mismatch). Cannot race with
519 setting other bitfields since it is only set once in INIT 522 setting other bitfields since it is only set once in INIT
520 reply, before any other request, and never cleared */ 523 reply, before any other request, and never cleared */
@@ -526,6 +529,9 @@ struct fuse_conn {
526 /** Do readpages asynchronously? Only set in INIT */ 529 /** Do readpages asynchronously? Only set in INIT */
527 unsigned async_read:1; 530 unsigned async_read:1;
528 531
532 /** Return an unique read error after abort. Only set in INIT */
533 unsigned abort_err:1;
534
529 /** Do not send separate SETATTR request before open(O_TRUNC) */ 535 /** Do not send separate SETATTR request before open(O_TRUNC) */
530 unsigned atomic_o_trunc:1; 536 unsigned atomic_o_trunc:1;
531 537
@@ -851,7 +857,7 @@ void fuse_request_send_background_locked(struct fuse_conn *fc,
851 struct fuse_req *req); 857 struct fuse_req *req);
852 858
853/* Abort all requests */ 859/* Abort all requests */
854void fuse_abort_conn(struct fuse_conn *fc); 860void fuse_abort_conn(struct fuse_conn *fc, bool is_abort);
855 861
856/** 862/**
857 * Invalidate inode attributes 863 * Invalidate inode attributes
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 624f18bbfd2b..3c9b675d99da 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -371,7 +371,7 @@ void fuse_unlock_inode(struct inode *inode)
371 371
372static void fuse_umount_begin(struct super_block *sb) 372static void fuse_umount_begin(struct super_block *sb)
373{ 373{
374 fuse_abort_conn(get_fuse_conn_super(sb)); 374 fuse_abort_conn(get_fuse_conn_super(sb), false);
375} 375}
376 376
377static void fuse_send_destroy(struct fuse_conn *fc) 377static void fuse_send_destroy(struct fuse_conn *fc)
@@ -393,7 +393,7 @@ static void fuse_put_super(struct super_block *sb)
393 393
394 fuse_send_destroy(fc); 394 fuse_send_destroy(fc);
395 395
396 fuse_abort_conn(fc); 396 fuse_abort_conn(fc, false);
397 mutex_lock(&fuse_mutex); 397 mutex_lock(&fuse_mutex);
398 list_del(&fc->entry); 398 list_del(&fc->entry);
399 fuse_ctl_remove_conn(fc); 399 fuse_ctl_remove_conn(fc);
@@ -918,6 +918,8 @@ static void process_init_reply(struct fuse_conn *fc, struct fuse_req *req)
918 fc->posix_acl = 1; 918 fc->posix_acl = 1;
919 fc->sb->s_xattr = fuse_acl_xattr_handlers; 919 fc->sb->s_xattr = fuse_acl_xattr_handlers;
920 } 920 }
921 if (arg->flags & FUSE_ABORT_ERROR)
922 fc->abort_err = 1;
921 } else { 923 } else {
922 ra_pages = fc->max_read / PAGE_SIZE; 924 ra_pages = fc->max_read / PAGE_SIZE;
923 fc->no_lock = 1; 925 fc->no_lock = 1;
@@ -948,7 +950,8 @@ static void fuse_send_init(struct fuse_conn *fc, struct fuse_req *req)
948 FUSE_FLOCK_LOCKS | FUSE_HAS_IOCTL_DIR | FUSE_AUTO_INVAL_DATA | 950 FUSE_FLOCK_LOCKS | FUSE_HAS_IOCTL_DIR | FUSE_AUTO_INVAL_DATA |
949 FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO | FUSE_ASYNC_DIO | 951 FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO | FUSE_ASYNC_DIO |
950 FUSE_WRITEBACK_CACHE | FUSE_NO_OPEN_SUPPORT | 952 FUSE_WRITEBACK_CACHE | FUSE_NO_OPEN_SUPPORT |
951 FUSE_PARALLEL_DIROPS | FUSE_HANDLE_KILLPRIV | FUSE_POSIX_ACL; 953 FUSE_PARALLEL_DIROPS | FUSE_HANDLE_KILLPRIV | FUSE_POSIX_ACL |
954 FUSE_ABORT_ERROR;
952 req->in.h.opcode = FUSE_INIT; 955 req->in.h.opcode = FUSE_INIT;
953 req->in.numargs = 1; 956 req->in.numargs = 1;
954 req->in.args[0].size = sizeof(*arg); 957 req->in.args[0].size = sizeof(*arg);
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 4b5001c57f46..92fa24c24c92 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -113,6 +113,9 @@
113 * 7.26 113 * 7.26
114 * - add FUSE_HANDLE_KILLPRIV 114 * - add FUSE_HANDLE_KILLPRIV
115 * - add FUSE_POSIX_ACL 115 * - add FUSE_POSIX_ACL
116 *
117 * 7.27
118 * - add FUSE_ABORT_ERROR
116 */ 119 */
117 120
118#ifndef _LINUX_FUSE_H 121#ifndef _LINUX_FUSE_H
@@ -148,7 +151,7 @@
148#define FUSE_KERNEL_VERSION 7 151#define FUSE_KERNEL_VERSION 7
149 152
150/** Minor version number of this interface */ 153/** Minor ver