io_uring: don't pass restart error codes to userspace

-ERESTARTSYS and friends are instructions for the system call exit code,
which either restarts the call or turns them into -EINTR. They are not
supposed to be seen by applications. A request isn't a system call,
nobody looks at what it returns before that is put into a CQE.

The read/write and the networking opcodes convert these codes to -EINTR
by hand. The others don't. An io-wq worker that is blocked in the open of
a FIFO, a tty or some other device, or in a splice from an empty pipe,
and that gets interrupted because the request is cancelled, completes it
with -512:

  IORING_OP_OPENAT, IOSQE_ASYNC       // FIFO without a writer
  IORING_OP_ASYNC_CANCEL              // -EALREADY, worker is signalled
  CQE of the open: res == -512

There are more candidates, like whatever a file system or driver returns
from ->flush(), ->fsync(), ->fallocate() or ->uring_cmd(), or breaking a
lease or delegation.

Take care of it in io_issue_sqe(), which all of them go through. The
helper is moved from rw.c as is. The cost is a compare and a branch that
is not taken per request.

Fixes: 15b71abe7b ("io_uring: add support for IORING_OP_OPENAT")
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Link: https://patch.msgid.link/20261015-b4-io-uring-fixes-v1-39-c1556f5d7bf5@toxicpanda.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
Josef Bacik 2026-10-15 19:31:03 +00:00 committed by Jens Axboe
parent a3d7ee1128
commit e1a64bc648
3 changed files with 23 additions and 18 deletions

View File

@ -1457,6 +1457,9 @@ static int io_issue_sqe(struct io_kiocb *req, unsigned int issue_flags)
ret = __io_issue_sqe(req, issue_flags, def);
if (ret == IOU_COMPLETE) {
/* not all handlers take care of the internal restart codes */
if (unlikely(req->cqe.res <= -ERESTARTSYS))
req->cqe.res = io_fixup_restart_res(req->cqe.res);
if (issue_flags & IO_URING_F_COMPLETE_DEFER)
io_req_complete_defer(req);
else
@ -1471,6 +1474,8 @@ static int io_issue_sqe(struct io_kiocb *req, unsigned int issue_flags)
if (req->flags & REQ_F_IOPOLL)
io_iopoll_req_issued(req, issue_flags);
}
if (unlikely(ret <= -ERESTARTSYS))
ret = io_fixup_restart_res(ret);
return ret;
}

View File

@ -319,6 +319,24 @@ static __always_inline bool io_fill_cqe_req(struct io_ring_ctx *ctx,
return true;
}
static inline ssize_t io_fixup_restart_res(ssize_t ret)
{
switch (ret) {
case -ERESTARTSYS:
case -ERESTARTNOINTR:
case -ERESTARTNOHAND:
case -ERESTART_RESTARTBLOCK:
/*
* We can't just restart the syscall, since previously
* submitted sqes may already be in progress. Just fail
* this IO with EINTR.
*/
return -EINTR;
default:
return ret;
}
}
static inline void *io_uring_alloc_async_data(struct io_alloc_cache *cache,
struct io_kiocb *req)
{

View File

@ -605,24 +605,6 @@ static void io_complete_rw_iopoll(struct kiocb *kiocb, long res)
smp_store_release(&req->iopoll_completed, 1);
}
static inline ssize_t io_fixup_restart_res(ssize_t ret)
{
switch (ret) {
case -ERESTARTSYS:
case -ERESTARTNOINTR:
case -ERESTARTNOHAND:
case -ERESTART_RESTARTBLOCK:
/*
* We can't just restart the syscall, since previously
* submitted sqes may already be in progress. Just fail
* this IO with EINTR.
*/
return -EINTR;
default:
return ret;
}
}
static inline void io_rw_done(struct io_kiocb *req, ssize_t ret)
{
struct io_rw *rw = io_kiocb_to_cmd(req, struct io_rw);