
From: Ma Jie Yue <majieyue@linux.alibaba.com> anolis inclusion category: feature bugzilla: https://gitee.com/openeuler/kernel/issues/ICFOA8 CVE: NA -------------------------------- Commit a07f55b5f686677333b96a8c188d6c6c6cd6e006 OpenAnolis In certain scenarios, to enhance availability, a new FUSE userspace daemon is launched to continue providing services after a crash of the previous daemon. This is achieved by performing an fd takeover from a dedicated watchdog daemon. However, if some inflight requests are lost during the crash, the application becomes stuck as it never receives a reply. To address this issue, this commit introduces a sysfs API that allows for flushing these pending requests after a daemon crash, prior to initiating the recovery procedure. Instead of remaining stuck, the flush operation returns an error to the application, causing the inflight requests to fail quickly. While returning an error may not be suitable for all scenarios, we have also submitted a separate patch to enable the ability to resend the inflight requests. Signed-off-by: Ma Jie Yue <majieyue@linux.alibaba.com> Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com> Reviewed-by: Xiaoguang Wang <xiaoguang.wang@linux.alibaba.com> Acked-by: Caspar Zhang <caspar@linux.alibaba.com> Signed-off-by: Hao Xu <haoxu@linux.alibaba.com> Signed-off-by: Zhao Chen <winters.zc@antgroup.com> Signed-off-by: Long Li <leo.lilong@huawei.com> --- fs/fuse/control.c | 20 ++++++++++++++++++++ fs/fuse/dev.c | 37 +++++++++++++++++++++++++++++++++++++ fs/fuse/fuse_i.h | 5 ++++- 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/fs/fuse/control.c b/fs/fuse/control.c index 1bf928e277fe..c0baf79977eb 100644 --- a/fs/fuse/control.c +++ b/fs/fuse/control.c @@ -44,6 +44,18 @@ static ssize_t fuse_conn_abort_write(struct file *file, const char __user *buf, return count; } +static ssize_t fuse_conn_flush_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct fuse_conn *fc = fuse_ctl_file_conn_get(file); + + if (fc) { + fuse_flush_pq(fc); + fuse_conn_put(fc); + } + return count; +} + static ssize_t fuse_conn_waiting_read(struct file *file, char __user *buf, size_t len, loff_t *ppos) { @@ -186,6 +198,12 @@ static const struct file_operations fuse_ctl_abort_ops = { .llseek = no_llseek, }; +static const struct file_operations fuse_ctl_flush_ops = { + .open = nonseekable_open, + .write = fuse_conn_flush_write, + .llseek = no_llseek, +}; + static const struct file_operations fuse_ctl_waiting_ops = { .open = nonseekable_open, .read = fuse_conn_waiting_read, @@ -270,6 +288,8 @@ int fuse_ctl_add_conn(struct fuse_conn *fc) NULL, &fuse_ctl_waiting_ops) || !fuse_ctl_add_dentry(parent, fc, "abort", S_IFREG | 0200, 1, NULL, &fuse_ctl_abort_ops) || + !fuse_ctl_add_dentry(parent, fc, "flush", S_IFREG | 0200, 1, + NULL, &fuse_ctl_flush_ops) || !fuse_ctl_add_dentry(parent, fc, "max_background", S_IFREG | 0600, 1, NULL, &fuse_conn_max_background_ops) || !fuse_ctl_add_dentry(parent, fc, "congestion_threshold", diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 9d6158d2da46..938c1e9f257b 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -2361,6 +2361,43 @@ int fuse_dev_release(struct inode *inode, struct file *file) } EXPORT_SYMBOL_GPL(fuse_dev_release); +/* + * Flush all pending processing requests. + * + * The failover procedure reuses the fuse_conn after a userspace crash and + * recovery. However, the requests in the processing queue will never receive a + * reply, causing the application to become stuck indefinitely. + * + * To resolve this issue, we need to flush these requests using the sysfs API. + * We only flush the requests in the processing queue, as these requests have + * already been sent to userspace. First, we dequeue the request from the + * processing queue, and then we call request_end to finalize it. + */ +void fuse_flush_pq(struct fuse_conn *fc) +{ + struct fuse_dev *fud; + LIST_HEAD(to_end); + unsigned int i; + + spin_lock(&fc->lock); + if (!fc->connected) { + spin_unlock(&fc->lock); + return; + } + list_for_each_entry(fud, &fc->devices, entry) { + struct fuse_pqueue *fpq = &fud->pq; + + spin_lock(&fpq->lock); + WARN_ON(!list_empty(&fpq->io)); + for (i = 0; i < FUSE_PQ_HASH_SIZE; i++) + list_splice_init(&fpq->processing[i], &to_end); + spin_unlock(&fpq->lock); + } + spin_unlock(&fc->lock); + + end_requests(&to_end); +} + static int fuse_dev_fasync(int fd, struct file *file, int on) { struct fuse_dev *fud = fuse_get_dev(file); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 665e89d8ea5b..346034ef02bd 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -46,7 +46,7 @@ #define FUSE_NAME_MAX 1024 /** Number of dentries for each connection in the control filesystem */ -#define FUSE_CTL_NUM_DENTRIES 5 +#define FUSE_CTL_NUM_DENTRIES 6 /** Maximum of max_pages received in init_out */ extern unsigned int fuse_max_pages_limit; @@ -1200,6 +1200,9 @@ void fuse_request_end(struct fuse_req *req); void fuse_abort_conn(struct fuse_conn *fc); void fuse_wait_aborted(struct fuse_conn *fc); +/* Flush all requests in processing queue */ +void fuse_flush_pq(struct fuse_conn *fc); + /** * Invalidate inode attributes */ -- 2.39.2