diff --git a/include/clone_internal.h b/include/clone_internal.h
index 567160ebb5..001797fccd 100644
--- a/include/clone_internal.h
+++ b/include/clone_internal.h
@@ -1,6 +1,7 @@
 #ifndef _CLONE_INTERNAL_H
 #define _CLONE_INTERNAL_H
 
+#include <stdbool.h>
 #include <clone3.h>
 
 /* The clone3 syscall provides a superset of the functionality of the clone
diff --git a/include/spawn.h b/include/spawn.h
index 4a0b1849da..95375337e4 100644
--- a/include/spawn.h
+++ b/include/spawn.h
@@ -2,6 +2,8 @@
 #include <posix/spawn.h>
 
 # ifndef _ISOMAC
+#  include <sys/wait.h>
+
 __typeof (posix_spawn) __posix_spawn;
 libc_hidden_proto (__posix_spawn)
 
@@ -35,5 +37,36 @@ __typeof (posix_spawnattr_setsigdefault) __posix_spawnattr_setsigdefault
 __typeof (posix_spawnattr_setsigmask) __posix_spawnattr_setsigmask
   attribute_hidden;
 
+typedef int process_create_id_t;
+_Static_assert (sizeof (process_create_id_t) == sizeof (pid_t),
+		"process_create_id_t must have same size as pid_t");
+
+/* Create a new process using pidfd_spawn or posix_spawn as a fallback, and
+   return an identifier that should be only be used with __spawn_process_wait.
+   The identifier is not changed if the process creation fails and the
+   function returns the same error code as {pidfd,posix}_spawn.  */
+int __spawn_process_create (process_create_id_t *,
+			    const char *__restric__,
+			    const posix_spawn_file_actions_t *__restrict,
+			    const posix_spawnattr_t *__restrict,
+			    char *const [__restrict_arr],
+			    char *const [__restrict_arr])
+     attribute_hidden;
+
+/* Wait for a process created with process_create_id_t, and return the status
+   code as for waitpid in second argument.  The third argument is the options
+   to be used, for instance WNOHANG.
+
+   It returns either the process id returned by __spawn_process_create for
+   the case of pidfd, or the pid_t if the fallback is used.  This semantic
+   allows to use this in place of waitpid calls.  */
+process_create_id_t __spawn_process_wait (process_create_id_t, int *, int)
+     attribute_hidden;
+
+/* Send a signal to the created process using either pidfd_send_signal or
+   kill.  */
+int __spawn_process_kill (process_create_id_t, int)
+     attribute_hidden;
+
 # endif /* !_ISOMAC  */
 #endif /* spawn.h  */
diff --git a/libio/iopopen.c b/libio/iopopen.c
index 37b6b1386b..9f39dc858f 100644
--- a/libio/iopopen.c
+++ b/libio/iopopen.c
@@ -40,7 +40,7 @@ struct _IO_proc_file
 {
   struct _IO_FILE_plus file;
   /* Following fields must match those in class procbuf (procbuf.h) */
-  pid_t pid;
+  process_create_id_t procid;
   struct _IO_proc_file *next;
 };
 typedef struct _IO_proc_file _IO_proc_file;
@@ -106,9 +106,16 @@ spawn_process (posix_spawn_file_actions_t *fa, FILE *fp, const char *command,
 	}
     }
 
-  err = __posix_spawn (&((_IO_proc_file *) fp)->pid, _PATH_BSHELL, fa, NULL,
-		       (char *const[]){ (char*) "sh", (char*) "-c", (char*) "--",
-		       (char *) command, NULL }, __environ);
+  err = __spawn_process_create (&((_IO_proc_file *) fp)->procid,
+				_PATH_BSHELL,
+				fa,
+				NULL,
+				(char *const[]){ (char*) "sh",
+						 (char*) "-c",
+						 (char*) "--",
+						 (char *) command,
+						 NULL },
+				__environ);
   if (err != 0)
     return err;
 
@@ -274,7 +281,6 @@ _IO_new_proc_close (FILE *fp)
   /* This is not name-space clean. FIXME! */
   int wstatus;
   _IO_proc_file **ptr = &proc_file_chain;
-  pid_t wait_pid;
   int status = -1;
 
   /* Unlink from proc_file_chain. */
@@ -306,11 +312,12 @@ _IO_new_proc_close (FILE *fp)
     {
       int state;
       __pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &state);
-      wait_pid = __waitpid (((_IO_proc_file *) fp)->pid, &wstatus, 0);
+      status =__spawn_process_wait (((_IO_proc_file *) fp)->procid,
+				    &wstatus, 0);
       __pthread_setcancelstate (state, NULL);
     }
-  while (wait_pid == -1 && errno == EINTR);
-  if (wait_pid == -1)
+  while (status == -1 && errno == EINTR);
+  if (status == -1)
     return -1;
   return wstatus;
 }
diff --git a/posix/Makefile b/posix/Makefile
index 0fa532396f..af5a5d9960 100644
--- a/posix/Makefile
+++ b/posix/Makefile
@@ -155,6 +155,7 @@ routines := \
   spawn_faction_addtcsetpgrp_np \
   spawn_faction_destroy \
   spawn_faction_init \
+  spawn_process \
   spawn_valid_fd \
   spawnattr_destroy \
   spawnattr_getdefault \
@@ -350,7 +351,12 @@ tests += \
   # tests
 endif
 
+tests-static-internal := \
+  tst-spawn_process-fd-exhaustion \
+  # tests-static-internal
+
 tests-internal := \
+  $(tests-static-internal)\
   bug-regex5 \
   bug-regex20 \
   bug-regex33 \
@@ -397,6 +403,7 @@ tests += \
 endif
 
 tests-static = \
+  $(tests-static-internal) \
   tst-exec-static \
   tst-libc-message \
   tst-spawn-static \
@@ -803,3 +810,5 @@ tst-wordexp-reuse-ENV += MALLOC_TRACE=$(objpfx)tst-wordexp-reuse.mtrace \
 $(objpfx)tst-wordexp-reuse-mem.out: $(objpfx)tst-wordexp-reuse.out
 	$(common-objpfx)malloc/mtrace $(objpfx)tst-wordexp-reuse.mtrace > $@; \
 	$(evaluate-test)
+
+CFLAGS-tst-spawn_process-fd-exhaustion.c += -DOBJPFX=\"$(objpfx)\"
diff --git a/posix/tst-spawn_process-fd-exhaustion.c b/posix/tst-spawn_process-fd-exhaustion.c
new file mode 100644
index 0000000000..98c1fea515
--- /dev/null
+++ b/posix/tst-spawn_process-fd-exhaustion.c
@@ -0,0 +1,120 @@
+/* Check if __spawn_process_create works if there is no available
+   file descriptor.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <intprops.h>
+#include <paths.h>
+#include <spawn.h>
+#include <stdlib.h>
+#include <sys/resource.h>
+
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/xunistd.h>
+
+#include <stdio.h>
+
+static const char pidfile[] = OBJPFX "tst-spawn_process-fd-exhaustion.pid";
+
+static int
+do_test (void)
+{
+  struct rlimit rl;
+  int max_fd = 24;
+
+  if (getrlimit (RLIMIT_NOFILE, &rl) == -1)
+    FAIL_EXIT1 ("getrlimit (RLIMIT_NOFILE): %m");
+
+  max_fd = (rl.rlim_cur < max_fd ? rl.rlim_cur : max_fd);
+  rl.rlim_cur = max_fd;
+
+  if (setrlimit (RLIMIT_NOFILE, &rl) == -1)
+    FAIL_EXIT1 ("setrlimit (RLIMIT_NOFILE): %m");
+
+  /* Exhauste the file descriptor limit with temporary files.  */
+  int files[max_fd];
+  int nfiles = 0;
+  for (; nfiles < max_fd; nfiles++)
+    {
+      int fd = create_temp_file ("tst-spawn_process-fd-exhaustion.pid.", NULL);
+      if (fd == -1)
+	{
+	  if (errno != EMFILE)
+	    FAIL_EXIT1 ("create_temp_file: %m");
+	  break;
+	}
+      int flags = fcntl (fd, F_GETFD, 0);
+      TEST_VERIFY_EXIT (flags != -1);
+      TEST_VERIFY_EXIT (fcntl (fd, F_SETFD, flags | FD_CLOEXEC) != -1);
+      files[nfiles] = fd;
+    }
+  TEST_VERIFY_EXIT (nfiles != 0);
+
+  process_create_id_t pid;
+  {
+    posix_spawn_file_actions_t fa;
+    TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0);
+    TEST_COMPARE (posix_spawn_file_actions_addopen (&fa, STDOUT_FILENO,
+						    pidfile,
+						    O_WRONLY| O_CREAT
+						    | O_TRUNC,
+						    0644), 0);
+
+    TEST_COMPARE (posix_spawn_file_actions_adddup2 (&fa, STDOUT_FILENO,
+						    STDERR_FILENO), 0);
+    char *spawn_argv[] =
+      {
+	(char *) _PATH_BSHELL,
+	(char *) "-c",
+	(char *) "echo $$",
+	NULL
+      };
+    int r = __spawn_process_create (&pid, _PATH_BSHELL, &fa, NULL,
+				    spawn_argv, NULL);
+    TEST_COMPARE (r, 0);
+
+    int status;
+    TEST_COMPARE (__spawn_process_wait (pid, &status, 0), pid);
+    TEST_COMPARE (WIFEXITED (status), 1);
+    TEST_COMPARE (WEXITSTATUS (status), 0);
+  }
+
+  for (int i=0; i<nfiles; i++)
+    xclose (files[i]);
+
+  {
+    int pidfd = xopen (pidfile, O_RDONLY, 0);
+
+    char buf[INT_BUFSIZE_BOUND (pid_t)];
+    ssize_t n = read (pidfd, buf, sizeof (buf));
+    TEST_VERIFY (n < sizeof buf && n >= 0);
+
+    /* We only expect to read the PID.  */
+    char *endp;
+    long int rpid = strtol (buf, &endp, 10);
+    TEST_VERIFY (*endp == '\n' && endp != buf);
+
+    TEST_COMPARE (rpid, pid);
+  }
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/posix/wordexp.c b/posix/wordexp.c
index 4a8541add4..a4d7040612 100644
--- a/posix/wordexp.c
+++ b/posix/wordexp.c
@@ -804,10 +804,11 @@ parse_arith (char **word, size_t *word_length, size_t *max_length,
 #include <malloc/dynarray-skeleton.c>
 
 /* Function called by child process in exec_comm() */
-static pid_t
-exec_comm_child (char *comm, int *fildes, bool showerr, bool noexec)
+static bool
+exec_comm_child (process_create_id_t *procid, char *comm, int *fildes,
+		 bool showerr, bool noexec)
 {
-  pid_t pid = -1;
+  bool r = false;
 
   /* Execute the command, or just check syntax?  */
   const char *args[] = { _PATH_BSHELL, noexec ? "-nc" : "-c", comm, NULL };
@@ -855,17 +856,17 @@ exec_comm_child (char *comm, int *fildes, bool showerr, bool noexec)
 	goto out;
     }
 
-  /* pid is not set if posix_spawn fails, so it keep the original value
-     of -1.  */
-  __posix_spawn (&pid, _PATH_BSHELL, &fa, NULL, (char *const *) args,
-		 recreate_env ? strlist_begin (&newenv) : __environ);
+  r = __spawn_process_create (procid, _PATH_BSHELL, &fa, NULL,
+			      (char *const *) args,
+			      recreate_env
+			      ? strlist_begin (&newenv) : __environ) == 0;
 
   strlist_free (&newenv);
 
 out:
   __posix_spawn_file_actions_destroy (&fa);
 
-  return pid;
+  return r;
 }
 
 /* Function to execute a command and retrieve the results */
@@ -882,7 +883,7 @@ exec_comm (char *comm, char **word, size_t *word_length, size_t *max_length,
   int status = 0;
   size_t maxnewlines = 0;
   char buffer[bufsize];
-  pid_t pid;
+  process_create_id_t pid;
   bool noexec = false;
 
   /* Do nothing if command substitution should not succeed.  */
@@ -897,9 +898,8 @@ exec_comm (char *comm, char **word, size_t *word_length, size_t *max_length,
     return WRDE_NOSPACE;
 
  again:
-  pid = exec_comm_child (comm, fildes, noexec ? false : flags & WRDE_SHOWERR,
-			 noexec);
-  if (pid < 0)
+  if (!exec_comm_child (&pid, comm, fildes,
+			noexec ? false : flags & WRDE_SHOWERR, noexec))
     {
       __close (fildes[0]);
       __close (fildes[1]);
@@ -908,7 +908,7 @@ exec_comm (char *comm, char **word, size_t *word_length, size_t *max_length,
 
   /* If we are just testing the syntax, only wait.  */
   if (noexec)
-    return (TEMP_FAILURE_RETRY (__waitpid (pid, &status, 0)) == pid
+    return (TEMP_FAILURE_RETRY (__spawn_process_wait (pid, &status, 0)) == pid
 	    && status != 0) ? WRDE_SYNTAX : 0;
 
   __close (fildes[1]);
@@ -925,8 +925,10 @@ exec_comm (char *comm, char **word, size_t *word_length, size_t *max_length,
 	      /* If read returned 0 then the process has closed its
 		 stdout.  Don't use WNOHANG in that case to avoid busy
 		 looping until the process eventually exits.  */
-	      if (TEMP_FAILURE_RETRY (__waitpid (pid, &status,
-						 buflen == 0 ? 0 : WNOHANG))
+	      if (TEMP_FAILURE_RETRY (__spawn_process_wait (pid,
+							    &status,
+							    buflen == 0
+							    ? 0 : WNOHANG))
 		  == 0)
 		continue;
 	      if ((buflen = TEMP_FAILURE_RETRY (__read (fildes[0], buffer,
@@ -960,8 +962,9 @@ exec_comm (char *comm, char **word, size_t *word_length, size_t *max_length,
 	      /* If read returned 0 then the process has closed its
 		 stdout.  Don't use WNOHANG in that case to avoid busy
 		 looping until the process eventually exits.  */
-	      if (TEMP_FAILURE_RETRY (__waitpid (pid, &status,
-						 buflen == 0 ? 0 : WNOHANG))
+	      if (TEMP_FAILURE_RETRY (__spawn_process_wait (pid, &status,
+							    buflen == 0
+							    ? 0 : WNOHANG))
 		  == 0)
 		continue;
 	      if ((buflen = TEMP_FAILURE_RETRY (__read (fildes[0], buffer,
@@ -1094,7 +1097,7 @@ exec_comm (char *comm, char **word, size_t *word_length, size_t *max_length,
 
 no_space:
   __kill (pid, SIGKILL);
-  TEMP_FAILURE_RETRY (__waitpid (pid, NULL, 0));
+  TEMP_FAILURE_RETRY (__spawn_process_wait (pid, NULL, 0));
   __close (fildes[0]);
   return WRDE_NOSPACE;
 }
diff --git a/sysdeps/generic/spawn_process.c b/sysdeps/generic/spawn_process.c
new file mode 100644
index 0000000000..5ca8d6260d
--- /dev/null
+++ b/sysdeps/generic/spawn_process.c
@@ -0,0 +1,43 @@
+/* Internal implementation of __spawn_process*.  Generic implementation.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <spawn.h>
+#include <not-errno.h>
+
+int
+__spawn_process_create (process_create_id_t *procid,
+			const char *path,
+			const posix_spawn_file_actions_t *facts,
+			const posix_spawnattr_t *attr,
+			char *const argv[],
+			char *const envp[])
+{
+  return __posix_spawn (procid, path, facts, attr, argv, envp);
+}
+
+int
+__spawn_process_kill (process_create_id_t procid, int signo)
+{
+  return __kill (procid, signo);
+}
+
+process_create_id_t
+__spawn_process_wait (process_create_id_t procid, int *wstatus, int options)
+{
+  return __waitpid (procid, wstatus, options | WEXITED);
+}
diff --git a/sysdeps/posix/system.c b/sysdeps/posix/system.c
index d01ee518ae..73fb3b3ec1 100644
--- a/sysdeps/posix/system.c
+++ b/sysdeps/posix/system.c
@@ -45,7 +45,7 @@
    last thread will restore them.
 
    Cancellation handling is done with thread cancellation clean-up handlers
-   on waitpid call.  */
+   on __spawn_process_wait call.  */
 
 #ifdef _LIBC_REENTRANT
 static struct sigaction intr, quit;
@@ -71,7 +71,7 @@ struct cancel_handler_args
 {
   struct sigaction *quit;
   struct sigaction *intr;
-  pid_t pid;
+  process_create_id_t pid;
 };
 
 static void
@@ -79,11 +79,11 @@ cancel_handler (void *arg)
 {
   struct cancel_handler_args *args = (struct cancel_handler_args *) (arg);
 
-  __kill_noerrno (args->pid, SIGKILL);
+  __spawn_process_kill (args->pid, SIGKILL);
 
   int state;
   __pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &state);
-  TEMP_FAILURE_RETRY (__waitpid (args->pid, NULL, 0));
+  TEMP_FAILURE_RETRY (__spawn_process_wait (args->pid, NULL, 0));
   __pthread_setcancelstate (state, NULL);
 
   DO_LOCK ();
@@ -102,7 +102,7 @@ do_system (const char *line)
 {
   int status = -1;
   int ret;
-  pid_t pid;
+  process_create_id_t pid;
   struct sigaction sa;
 #ifndef _LIBC_REENTRANT
   struct sigaction intr, quit;
@@ -144,12 +144,13 @@ do_system (const char *line)
   __posix_spawnattr_setflags (&spawn_attr,
 			      POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK);
 
-  ret = __posix_spawn (&pid, SHELL_PATH, NULL, &spawn_attr,
-		       (char *const[]){ (char *) SHELL_NAME,
-					(char *) "-c",
-					(char *) "--",
-					(char *) line, NULL },
-		       __environ);
+  ret = __spawn_process_create (&pid, SHELL_PATH, NULL, &spawn_attr,
+				(char *const[]){ (char *) SHELL_NAME,
+						 (char *) "-c",
+						 (char *) "--",
+						 (char *) line,
+						 NULL },
+				__environ);
   __posix_spawnattr_destroy (&spawn_attr);
 
   if (ret == 0)
@@ -169,7 +170,7 @@ do_system (const char *line)
       /* Note the system() is a cancellation point.  But since we call
 	 waitpid() which itself is a cancellation point we do not
 	 have to do anything here.  */
-      if (TEMP_FAILURE_RETRY (__waitpid (pid, &status, 0)) != pid)
+      if (TEMP_FAILURE_RETRY (__spawn_process_wait (pid, &status, 0)) != pid)
 	status = -1;
 #if defined(_LIBC_REENTRANT) && defined(SIGCANCEL)
       __libc_cleanup_region_end (0);
diff --git a/sysdeps/unix/sysv/linux/include/bits/spawn_ext.h b/sysdeps/unix/sysv/linux/include/bits/spawn_ext.h
new file mode 100644
index 0000000000..a7da319287
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/include/bits/spawn_ext.h
@@ -0,0 +1,11 @@
+#ifndef _SPAWN_EXT
+# define _SPAWN_EXT
+
+#include_next <bits/spawn_ext.h>
+
+#  ifndef _ISOMAC
+__typeof (pidfd_spawn) __pidfd_spawn;
+libc_hidden_proto (__pidfd_spawn);
+# endif
+
+#endif
diff --git a/sysdeps/unix/sysv/linux/not-errno.h b/sysdeps/unix/sysv/linux/not-errno.h
index dc484bd3f5..00992268eb 100644
--- a/sysdeps/unix/sysv/linux/not-errno.h
+++ b/sysdeps/unix/sysv/linux/not-errno.h
@@ -28,3 +28,14 @@ __kill_noerrno (pid_t pid, int sig)
     return INTERNAL_SYSCALL_ERRNO (res);
   return 0;
 }
+
+static inline int
+__pidfd_send_signal_noerrno (int pidfd, int sig, siginfo_t *info,
+			     unsigned int flags)
+{
+  int res;
+  res = INTERNAL_SYSCALL_CALL (pidfd_send_signal, pidfd, sig, info, flags);
+  if (INTERNAL_SYSCALL_ERROR_P (res))
+    return INTERNAL_SYSCALL_ERRNO (res);
+  return 0;
+}
diff --git a/sysdeps/unix/sysv/linux/pidfd_spawn.c b/sysdeps/unix/sysv/linux/pidfd_spawn.c
index a2e0a70017..fec1f29f89 100644
--- a/sysdeps/unix/sysv/linux/pidfd_spawn.c
+++ b/sysdeps/unix/sysv/linux/pidfd_spawn.c
@@ -20,11 +20,13 @@
 #include "spawn_int.h"
 
 int
-pidfd_spawn (int *pidfd, const char *path,
-	     const posix_spawn_file_actions_t *file_actions,
-	     const posix_spawnattr_t *attrp, char *const argv[],
-	     char *const envp[])
+__pidfd_spawn (int *pidfd, const char *path,
+	       const posix_spawn_file_actions_t *file_actions,
+	       const posix_spawnattr_t *attrp, char *const argv[],
+	       char *const envp[])
 {
   return __spawni (pidfd, path, file_actions, attrp, argv, envp,
 		   SPAWN_XFLAGS_RET_PIDFD);
 }
+libc_hidden_def (__pidfd_spawn)
+weak_alias (__pidfd_spawn, pidfd_spawn)
diff --git a/sysdeps/unix/sysv/linux/spawn_process.c b/sysdeps/unix/sysv/linux/spawn_process.c
new file mode 100644
index 0000000000..c525b9f8d7
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/spawn_process.c
@@ -0,0 +1,121 @@
+/* Internal implementation of __spawn_process*.  Linux implementation.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <spawn.h>
+#include <clone_internal.h>
+#include <not-errno.h>
+
+#pragma GCC optimize ("O0")
+
+int
+__spawn_process_create (process_create_id_t *procid,
+			const char *path,
+			const posix_spawn_file_actions_t *facts,
+			const posix_spawnattr_t *attr,
+			char *const argv[],
+			char *const envp[])
+{
+  int r;
+
+  if (__clone_pidfd_supported ())
+    {
+      int pidfd;
+      r = __pidfd_spawn (&pidfd, path, facts, attr, argv, envp);
+      if (r == 0)
+	{
+	  /* Both pidfd and pid_t do not allow negative values to describe a
+	     new process, so use the MSB to set the identifier is for
+	     pidfd.  */
+	  *procid = pidfd | 0x80000000;
+	  return 0;
+	}
+
+      /* Fallback to posix_spawn if file descriptor limits is reached.  */
+      if (r != EMFILE && r != ENFILE)
+	return r;
+    }
+
+  pid_t pid;
+  r = __posix_spawn (&pid, path, facts, attr, argv, envp);
+  if (r != 0)
+    return r;
+  *procid = pid;
+  return 0;
+}
+
+int
+__spawn_process_kill (process_create_id_t procid, int signo)
+{
+  return procid & 0x80000000
+    ? __pidfd_send_signal_noerrno (procid & INT_MAX, signo, NULL, 0)
+    : __kill_noerrno (procid, signo);
+}
+
+process_create_id_t
+__spawn_process_wait (process_create_id_t procid, int *wstatus, int options)
+{
+  bool use_pidfd = procid & 0x80000000;
+
+  siginfo_t info = { 0 };
+  int waitid_opts = WEXITED;
+  if (options & WNOHANG)
+    waitid_opts |= WNOHANG;
+  if (options & WUNTRACED)
+    waitid_opts |= WSTOPPED;
+  if (options & WCONTINUED)
+    waitid_opts |= WCONTINUED;
+
+  if (__waitid (use_pidfd ? P_PIDFD : P_PID,
+		use_pidfd ? procid & INT_MAX : procid,
+		&info,
+		waitid_opts) == -1)
+    return -1;
+
+  /* Handle successful WNOHANG but without a child state change.  */
+  if (info.si_pid == 0)
+    return 0;
+
+  if (wstatus != NULL)
+    {
+      int status = 0;
+      switch (info.si_code)
+	{
+	case CLD_EXITED:
+	  status = (info.si_status & 0xff) << 8;
+	  break;
+	case CLD_KILLED:
+	  status = info.si_status & 0x7f;
+	  break;
+	case CLD_DUMPED:
+	  status = (info.si_status & 0x7f) | __WCOREFLAG;
+	  break;
+	case CLD_STOPPED:
+	case CLD_TRAPPED:
+	  status = ((info.si_status & 0xff) << 8) | 0x7f;
+	  break;
+	case CLD_CONTINUED:
+	  status = 0xffff;
+	  break;
+        }
+      *wstatus = status;
+    }
+
+  /* With P_PIDFD, waitid populates info.si_pid with the actual Process ID of
+     the child.  */
+  return use_pidfd ? procid : info.si_pid;
+}
