mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Nickolai Zeldovich <nickolai@csail.mit.edu>
To: Jens Axboe <axboe@kernel.dk>
Cc: Christian Brauner <brauner@kernel.org>,
	io-uring@vger.kernel.org, linux-kernel@vger.kernel.org,
	Nickolai Zeldovich <nickolai@csail.mit.edu>
Subject: [PATCH v1] io_uring/waitid: return -EAGAIN for a non-blocking pidfd
Date: Sat, 17 Oct 2026 22:58:24 -0400	[thread overview]
Message-ID: <20261018025824.799245-1-nickolai@csail.mit.edu> (raw)

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 <nickolai@csail.mit.edu>
---

Notes:
    Reproducer (CONFIG_IO_URING=y; no liburing needed):
    
      #define _GNU_SOURCE
      #include <errno.h>
      #include <fcntl.h>
      #include <linux/io_uring.h>
      #include <signal.h>
      #include <stdio.h>
      #include <string.h>
      #include <sys/mman.h>
      #include <sys/syscall.h>
      #include <sys/wait.h>
      #include <unistd.h>
    
      #ifndef PIDFD_NONBLOCK
      #define PIDFD_NONBLOCK O_NONBLOCK
      #endif
    
      int main(void)
      {
      	struct io_uring_params p = { 0 };
      	struct io_uring_sqe *sqes;
      	struct io_uring_cqe *cqe;
      	siginfo_t si;
      	unsigned int *sq_tail, *sq_array, *cq_head;
      	void *sq, *cq;
      	int ring, pidfd, ret;
      	pid_t child;
    
      	child = fork();
      	if (child == 0) {
      		pause();
      		_exit(0);
      	}
      	pidfd = syscall(SYS_pidfd_open, child, PIDFD_NONBLOCK);
    
      	memset(&si, 0xff, sizeof(si));
      	ret = syscall(SYS_waitid, P_PIDFD, pidfd, &si, WEXITED, NULL);
      	printf("waitid(2):        ret=%d errno=%s si_signo=%d\n", ret,
      	       ret ? strerror(errno) : "-", si.si_signo);
    
      	ring = syscall(SYS_io_uring_setup, 4, &p);
      	sq = mmap(NULL, p.sq_off.array + p.sq_entries * 4, PROT_READ | PROT_WRITE,
      		  MAP_SHARED, ring, IORING_OFF_SQ_RING);
      	cq = mmap(NULL, p.cq_off.cqes + p.cq_entries * sizeof(*cqe),
      		  PROT_READ | PROT_WRITE, MAP_SHARED, ring, IORING_OFF_CQ_RING);
      	sqes = mmap(NULL, p.sq_entries * sizeof(*sqes), PROT_READ | PROT_WRITE,
      		    MAP_SHARED, ring, IORING_OFF_SQES);
      	sq_tail = sq + p.sq_off.tail;
      	sq_array = sq + p.sq_off.array;
      	cq_head = cq + p.cq_off.head;
    
      	memset(&si, 0xff, sizeof(si));
      	memset(&sqes[0], 0, sizeof(sqes[0]));
      	sqes[0].opcode = IORING_OP_WAITID;
      	sqes[0].fd = pidfd;		/* id */
      	sqes[0].len = P_PIDFD;		/* idtype */
      	sqes[0].file_index = WEXITED;	/* options */
      	sqes[0].addr2 = (unsigned long)&si;
      	sq_array[0] = 0;
      	__atomic_store_n(sq_tail, 1, __ATOMIC_RELEASE);
      	ret = syscall(SYS_io_uring_enter, ring, 1, 1, IORING_ENTER_GETEVENTS, NULL, 0);
      	cqe = cq + p.cq_off.cqes;
      	printf("IORING_OP_WAITID: res=%d (%s) si_signo=%d\n", cqe->res,
      	       cqe->res ? strerror(-cqe->res) : "-", si.si_signo);
      	__atomic_store_n(cq_head, 1, __ATOMIC_RELEASE);
    
      	kill(child, SIGKILL);
      	waitpid(child, NULL, 0);
      	return 0;
      }
    
      gcc -O2 -static -o waitid-nonblock waitid-nonblock.c && ./waitid-nonblock
    
    Before:
      waitid(2):        ret=-1 errno=Resource temporarily unavailable si_signo=0
      IORING_OP_WAITID: res=0 (-) si_signo=0
    
    After:
      waitid(2):        ret=-1 errno=Resource temporarily unavailable si_signo=0
      IORING_OP_WAITID: res=-11 (Resource temporarily unavailable) si_signo=0

 io_uring/waitid.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/io_uring/waitid.c b/io_uring/waitid.c
index 2b756017423f..b99faf779766 100644
--- a/io_uring/waitid.c
+++ b/io_uring/waitid.c
@@ -10,6 +10,7 @@
 #include <linux/io_uring.h>
 
 #include <uapi/linux/io_uring.h>
+#include <uapi/linux/wait.h>
 
 #include "io_uring.h"
 #include "cancel.h"
@@ -357,6 +358,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);

base-commit: 58161ead29e50d70a317ea26b472e0c7508e69da
-- 
2.53.0


             reply	other threads:[~2026-10-18  2:58 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-18  2:58 Nickolai Zeldovich [this message]
2026-10-19 21:02 ` Jens Axboe

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261018025824.799245-1-nickolai@csail.mit.edu \
    --to=nickolai@csail.mit.edu \
    --cc=axboe@kernel.dk \
    --cc=brauner@kernel.org \
    --cc=io-uring@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®