| From f254ac04c8744cf7bfed012717eac34eacc65dfb Mon Sep 17 00:00:00 2001 |
| From: Jens Axboe <axboe@kernel.dk> |
| Date: Wed, 12 Aug 2020 17:33:30 -0600 |
| Subject: io_uring: enable lookup of links holding inflight files |
| |
| From: Jens Axboe <axboe@kernel.dk> |
| |
| commit f254ac04c8744cf7bfed012717eac34eacc65dfb upstream. |
| |
| When a process exits, we cancel whatever requests it has pending that |
| are referencing the file table. However, if a link is holding a |
| reference, then we cannot find it by simply looking at the inflight |
| list. |
| |
| Enable checking of the poll and timeout list to find the link, and |
| cancel it appropriately. |
| |
| Cc: stable@vger.kernel.org |
| Reported-by: Josef <josef.grieb@gmail.com> |
| Signed-off-by: Jens Axboe <axboe@kernel.dk> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| |
| --- |
| fs/io_uring.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ |
| 1 file changed, 87 insertions(+), 10 deletions(-) |
| |
| --- a/fs/io_uring.c |
| +++ b/fs/io_uring.c |
| @@ -4638,6 +4638,7 @@ static bool io_poll_remove_one(struct io |
| io_cqring_fill_event(req, -ECANCELED); |
| io_commit_cqring(req->ctx); |
| req->flags |= REQ_F_COMP_LOCKED; |
| + req_set_fail_links(req); |
| io_put_req(req); |
| } |
| |
| @@ -4820,6 +4821,23 @@ static enum hrtimer_restart io_timeout_f |
| return HRTIMER_NORESTART; |
| } |
| |
| +static int __io_timeout_cancel(struct io_kiocb *req) |
| +{ |
| + int ret; |
| + |
| + list_del_init(&req->list); |
| + |
| + ret = hrtimer_try_to_cancel(&req->io->timeout.timer); |
| + if (ret == -1) |
| + return -EALREADY; |
| + |
| + req_set_fail_links(req); |
| + req->flags |= REQ_F_COMP_LOCKED; |
| + io_cqring_fill_event(req, -ECANCELED); |
| + io_put_req(req); |
| + return 0; |
| +} |
| + |
| static int io_timeout_cancel(struct io_ring_ctx *ctx, __u64 user_data) |
| { |
| struct io_kiocb *req; |
| @@ -4827,7 +4845,6 @@ static int io_timeout_cancel(struct io_r |
| |
| list_for_each_entry(req, &ctx->timeout_list, list) { |
| if (user_data == req->user_data) { |
| - list_del_init(&req->list); |
| ret = 0; |
| break; |
| } |
| @@ -4836,15 +4853,7 @@ static int io_timeout_cancel(struct io_r |
| if (ret == -ENOENT) |
| return ret; |
| |
| - ret = hrtimer_try_to_cancel(&req->io->timeout.timer); |
| - if (ret == -1) |
| - return -EALREADY; |
| - |
| - req_set_fail_links(req); |
| - req->flags |= REQ_F_COMP_LOCKED; |
| - io_cqring_fill_event(req, -ECANCELED); |
| - io_put_req(req); |
| - return 0; |
| + return __io_timeout_cancel(req); |
| } |
| |
| static int io_timeout_remove_prep(struct io_kiocb *req, |
| @@ -7579,6 +7588,71 @@ static int io_uring_release(struct inode |
| return 0; |
| } |
| |
| +/* |
| + * Returns true if 'preq' is the link parent of 'req' |
| + */ |
| +static bool io_match_link(struct io_kiocb *preq, struct io_kiocb *req) |
| +{ |
| + struct io_kiocb *link; |
| + |
| + if (!(preq->flags & REQ_F_LINK_HEAD)) |
| + return false; |
| + |
| + list_for_each_entry(link, &preq->link_list, link_list) { |
| + if (link == req) |
| + return true; |
| + } |
| + |
| + return false; |
| +} |
| + |
| +/* |
| + * We're looking to cancel 'req' because it's holding on to our files, but |
| + * 'req' could be a link to another request. See if it is, and cancel that |
| + * parent request if so. |
| + */ |
| +static bool io_poll_remove_link(struct io_ring_ctx *ctx, struct io_kiocb *req) |
| +{ |
| + struct hlist_node *tmp; |
| + struct io_kiocb *preq; |
| + bool found = false; |
| + int i; |
| + |
| + spin_lock_irq(&ctx->completion_lock); |
| + for (i = 0; i < (1U << ctx->cancel_hash_bits); i++) { |
| + struct hlist_head *list; |
| + |
| + list = &ctx->cancel_hash[i]; |
| + hlist_for_each_entry_safe(preq, tmp, list, hash_node) { |
| + found = io_match_link(preq, req); |
| + if (found) { |
| + io_poll_remove_one(preq); |
| + break; |
| + } |
| + } |
| + } |
| + spin_unlock_irq(&ctx->completion_lock); |
| + return found; |
| +} |
| + |
| +static bool io_timeout_remove_link(struct io_ring_ctx *ctx, |
| + struct io_kiocb *req) |
| +{ |
| + struct io_kiocb *preq; |
| + bool found = false; |
| + |
| + spin_lock_irq(&ctx->completion_lock); |
| + list_for_each_entry(preq, &ctx->timeout_list, list) { |
| + found = io_match_link(preq, req); |
| + if (found) { |
| + __io_timeout_cancel(preq); |
| + break; |
| + } |
| + } |
| + spin_unlock_irq(&ctx->completion_lock); |
| + return found; |
| +} |
| + |
| static void io_uring_cancel_files(struct io_ring_ctx *ctx, |
| struct files_struct *files) |
| { |
| @@ -7629,6 +7703,9 @@ static void io_uring_cancel_files(struct |
| } |
| } else { |
| io_wq_cancel_work(ctx->io_wq, &cancel_req->work); |
| + /* could be a link, check and remove if it is */ |
| + if (!io_poll_remove_link(ctx, cancel_req)) |
| + io_timeout_remove_link(ctx, cancel_req); |
| io_put_req(cancel_req); |
| } |
| |