git: b4e2c9abaf14 - main - libcasper: tolerate kernels without PD_NOWAITPID

From: Xin LI <delphij_at_FreeBSD.org>
Date: Tue, 06 Oct 2026 08:11:31 UTC
The branch main has been updated by delphij:

URL: https://cgit.FreeBSD.org/src/commit/?id=b4e2c9abaf14d6eabbce59656d448c064e0dc40b

commit b4e2c9abaf14d6eabbce59656d448c064e0dc40b
Author:     Xin LI <delphij@FreeBSD.org>
AuthorDate: 2026-10-06 07:17:21 +0000
Commit:     Xin LI <delphij@FreeBSD.org>
CommitDate: 2026-10-06 08:10:57 +0000

    libcasper: tolerate kernels without PD_NOWAITPID
    
    Commit 1a296762b3d0 made libcasper pass PD_NOWAITPID to pdfork(2).
    Kernels predating the flag (15.1 and earlier, since the flag first
    ships in 15.2) reject it with EINVAL, which makes cap_init() and every
    service fork fail when a newer world runs on an older kernel, for
    example in a poudriere jail.
    
    Add casper_pdfork(), which retries without the flag on EINVAL, and use
    it at both pdfork(2) call sites.  The retry is safe because the kernel
    validates pdfork flags before creating a child.  On such kernels the
    zombie must still be reaped with waitpid(2), as before the flag was
    introduced.
    
    The fallback is compiled out once __FreeBSD_version reaches 1700000,
    so it disappears from main when stable/16 branches while remaining in
    the stable/15 and stable/16 branches that need it.
    
    Reviewed by:    kib
    MFC after:      3 days
    
    Differential Revision: https://reviews.freebsd.org/D60401
---
 lib/libcasper/libcasper/libcasper.c      |  2 +-
 lib/libcasper/libcasper/libcasper_impl.h | 23 +++++++++++++++++++++++
 lib/libcasper/libcasper/zygote.c         |  2 +-
 3 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/lib/libcasper/libcasper/libcasper.c b/lib/libcasper/libcasper/libcasper.c
index 8783529d9dc0..9a34b8d7acea 100644
--- a/lib/libcasper/libcasper/libcasper.c
+++ b/lib/libcasper/libcasper/libcasper.c
@@ -95,7 +95,7 @@ cap_init(void)
 		return (NULL);
 	}
 
-	pid = pdfork(&pfd, PD_NOWAITPID);
+	pid = casper_pdfork(&pfd);
 	if (pid == 0) {
 		/* Child. */
 		close(sock[0]);
diff --git a/lib/libcasper/libcasper/libcasper_impl.h b/lib/libcasper/libcasper/libcasper_impl.h
index 43d2495cacf6..682a71fff01c 100644
--- a/lib/libcasper/libcasper/libcasper_impl.h
+++ b/lib/libcasper/libcasper/libcasper_impl.h
@@ -33,6 +33,10 @@
 #ifndef	_LIBCASPER_IMPL_H_
 #define	_LIBCASPER_IMPL_H_
 
+#include <sys/param.h>
+#include <sys/procdesc.h>
+
+#include <errno.h>
 #include <stdbool.h>
 
 #include "libcasper.h"
@@ -44,6 +48,25 @@ struct service_connection;
 bool fd_is_valid(int fd);
 void fd_fix_environment(int *fdp);
 
+static inline pid_t
+casper_pdfork(int *fdp)
+{
+	pid_t pid;
+
+	pid = pdfork(fdp, PD_NOWAITPID);
+#if __FreeBSD_version < 1700000
+	/*
+	 * Kernels predating PD_NOWAITPID (introduced in 15.2) reject the
+	 * flag with EINVAL before creating a child, so retrying without
+	 * it is safe.  The child must then be reaped with waitpid(2), as
+	 * before.  This fallback is compiled out starting with 17.0.
+	 */
+	if (pid == -1 && errno == EINVAL)
+		pid = pdfork(fdp, 0);
+#endif
+	return (pid);
+}
+
 /* Private service functions. */
 struct service	*service_alloc(const char *name,
 		    service_limit_func_t *limitfunc,
diff --git a/lib/libcasper/libcasper/zygote.c b/lib/libcasper/libcasper/zygote.c
index 8295312b6593..574d80a16b01 100644
--- a/lib/libcasper/libcasper/zygote.c
+++ b/lib/libcasper/libcasper/zygote.c
@@ -149,7 +149,7 @@ zygote_main(int *sockp)
 			error = errno;
 			goto send;
 		}
-		pid = pdfork(&procfd, PD_NOWAITPID);
+		pid = casper_pdfork(&procfd);
 		switch (pid) {
 		case -1:
 			/* Failure. */