From c82621eb2c9a00b514a459951609e04843d467bb Mon Sep 17 00:00:00 2001 From: Nickolai Zeldovich Date: Sat, 17 Oct 2026 22:58:24 -0400 Subject: [PATCH] io_uring/waitid: return -EAGAIN for a non-blocking pidfd IORING_OP_WAITID on a pidfd opened with PIDFD_NONBLOCK completes at once with res 0 and an all-zero siginfo when the child has not changed state, although WNOHANG was not requested. waitid(2) with the same arguments fails with -EAGAIN. An application that submits the same request it would pass to waitid(2) and treats res 0 as "the child changed state" sees si_signo 0 and si_pid 0 instead. kernel_waitid_prepare() turns an O_NONBLOCK pidfd into WNOHANG in wo->wo_flags. kernel_waitid() then maps the resulting 0 return to -EAGAIN when the caller did not ask for WNOHANG itself, as added in commit ba7d25f3dff6 ("exit: support non-blocking pidfds"). io_waitid() uses kernel_waitid_prepare() and __do_wait() directly and does not do that mapping. Do the same check in io_waitid() before completing the request. Only the inline path needs it: with WNOHANG set, __do_wait() never returns -ERESTARTSYS, so such a request is never armed on the wait queue. Reproduced on x86_64 under QEMU: with a PIDFD_NONBLOCK pidfd of a running child and options = WEXITED, waitid(2) fails with -EAGAIN and IORING_OP_WAITID completed with res 0 before the change; it completes with res -EAGAIN after it. Fixes: f31ecf671ddc ("io_uring: add IORING_OP_WAITID support") Assisted-by: LLM Signed-off-by: Nickolai Zeldovich Link: https://patch.msgid.link/20261018025824.799245-1-nickolai@csail.mit.edu Signed-off-by: Jens Axboe --- io_uring/waitid.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/io_uring/waitid.c b/io_uring/waitid.c index 172cf770b29b..986fb46639e8 100644 --- a/io_uring/waitid.c +++ b/io_uring/waitid.c @@ -10,6 +10,7 @@ #include #include +#include #include "io_uring.h" #include "cancel.h" @@ -368,6 +369,9 @@ int io_waitid(struct io_kiocb *req, unsigned int issue_flags) hlist_del_init(&req->hash_node); io_waitid_remove_wq(req); + /* a non-blocking pidfd implies WNOHANG, report it as waitid(2) does */ + if (!ret && !(iw->options & WNOHANG) && (iwa->wo.wo_flags & WNOHANG)) + ret = -EAGAIN; ret = io_waitid_finish(req, ret); io_ring_submit_unlock(ctx, issue_flags);