| // SPDX-License-Identifier: GPL-2.0-only | |
| #include "dev.h" | |
| #include "sysctl.h" | |
| #include "fuse_dev_i.h" | |
| #include "dev_uring_i.h" | |
| /* Frequency (in seconds) of request timeout checks, if opted into */ | |
| #define FUSE_TIMEOUT_TIMER_FREQ 15 | |
| /* Frequency (in jiffies) of request timeout checks, if opted into */ | |
| static const unsigned long fuse_timeout_timer_freq = | |
| secs_to_jiffies(FUSE_TIMEOUT_TIMER_FREQ); | |
| /* | |
| * Default timeout (in seconds) for the server to reply to a request | |
| * before the connection is aborted, if no timeout was specified on mount. | |
| * | |
| * Exported via sysctl | |
| */ | |
| unsigned int fuse_default_req_timeout; | |
| /* | |
| * Max timeout (in seconds) for the server to reply to a request before | |
| * the connection is aborted. | |
| * | |
| * Exported via sysctl | |
| */ | |
| unsigned int fuse_max_req_timeout; | |
| bool fuse_request_expired(struct fuse_chan *fch, struct list_head *list) | |
| { | |
| struct fuse_req *req; | |
| req = list_first_entry_or_null(list, struct fuse_req, list); | |
| if (!req) | |
| return false; | |
| return time_is_before_jiffies(req->create_time + fch->timeout.req_timeout); | |
| } | |
| static bool fuse_fpq_processing_expired(struct fuse_chan *fch, struct list_head *processing) | |
| { | |
| int i; | |
| for (i = 0; i < FUSE_PQ_HASH_SIZE; i++) | |
| if (fuse_request_expired(fch, &processing[i])) | |
| return true; | |
| return false; | |
| } | |
| /* | |
| * Check if any requests aren't being completed by the time the request timeout | |
| * elapses. To do so, we: | |
| * - check the fiq pending list | |
| * - check the bg queue | |
| * - check the fpq io and processing lists | |
| * | |
| * To make this fast, we only check against the head request on each list since | |
| * these are generally queued in order of creation time (eg newer requests get | |
| * queued to the tail). We might miss a few edge cases (eg requests transitioning | |
| * between lists, re-sent requests at the head of the pending list having a | |
| * later creation time than other requests on that list, etc.) but that is fine | |
| * since if the request never gets fulfilled, it will eventually be caught. | |
| */ | |
| static void fuse_check_timeout(struct work_struct *work) | |
| { | |
| struct delayed_work *dwork = to_delayed_work(work); | |
| struct fuse_chan *fch = container_of(dwork, struct fuse_chan, timeout.work); | |
| struct fuse_iqueue *fiq = &fch->iq; | |
| struct fuse_dev *fud; | |
| struct fuse_pqueue *fpq; | |
| bool expired = false; | |
| if (!atomic_read(&fch->num_waiting)) | |
| goto out; | |
| spin_lock(&fiq->lock); | |
| expired = fuse_request_expired(fch, &fiq->pending); | |
| spin_unlock(&fiq->lock); | |
| if (expired) | |
| goto chan_abort; | |
| spin_lock(&fch->bg_lock); | |
| expired = fuse_request_expired(fch, &fch->bg_queue); | |
| spin_unlock(&fch->bg_lock); | |
| if (expired) | |
| goto chan_abort; | |
| spin_lock(&fch->lock); | |
| if (!fch->connected) { | |
| spin_unlock(&fch->lock); | |
| return; | |
| } | |
| list_for_each_entry(fud, &fch->devices, entry) { | |
| fpq = &fud->pq; | |
| spin_lock(&fpq->lock); | |
| if (fuse_request_expired(fch, &fpq->io) || | |
| fuse_fpq_processing_expired(fch, fpq->processing)) { | |
| spin_unlock(&fpq->lock); | |
| spin_unlock(&fch->lock); | |
| goto chan_abort; | |
| } | |
| spin_unlock(&fpq->lock); | |
| } | |
| spin_unlock(&fch->lock); | |
| if (fuse_uring_request_expired(fch)) | |
| goto chan_abort; | |
| out: | |
| queue_delayed_work(system_percpu_wq, &fch->timeout.work, | |
| fuse_timeout_timer_freq); | |
| return; | |
| chan_abort: | |
| fuse_chan_abort(fch, false); | |
| } | |
| static void set_request_timeout(struct fuse_chan *fch, unsigned int timeout) | |
| { | |
| fch->timeout.req_timeout = secs_to_jiffies(timeout); | |
| INIT_DELAYED_WORK(&fch->timeout.work, fuse_check_timeout); | |
| queue_delayed_work(system_percpu_wq, &fch->timeout.work, | |
| fuse_timeout_timer_freq); | |
| } | |
| void fuse_init_server_timeout(struct fuse_chan *fch, unsigned int timeout) | |
| { | |
| if (!timeout) | |
| timeout = fuse_default_req_timeout; | |
| timeout = min_not_zero(timeout, fuse_max_req_timeout); | |
| if (!timeout) | |
| return; | |
| timeout = max(FUSE_TIMEOUT_TIMER_FREQ, timeout); | |
| set_request_timeout(fch, timeout); | |
| } | |