{"id":2230514,"url":"http://patchwork.ozlabs.org/api/1.1/patches/2230514/?format=json","web_url":"http://patchwork.ozlabs.org/project/glibc/patch/20260429200041.3051117-1-adhemerval.zanella@linaro.org/","project":{"id":41,"url":"http://patchwork.ozlabs.org/api/1.1/projects/41/?format=json","name":"GNU C Library","link_name":"glibc","list_id":"libc-alpha.sourceware.org","list_email":"libc-alpha@sourceware.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260429200041.3051117-1-adhemerval.zanella@linaro.org>","date":"2026-04-29T20:00:10","name":"[RFC] posix: Make system, popen, and wordexp try pidfd_spawn first (BZ 34001)","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"91618ccf21f66f74f45be31ecc88dd86fe86aee2","submitter":{"id":66065,"url":"http://patchwork.ozlabs.org/api/1.1/people/66065/?format=json","name":"Adhemerval Zanella Netto","email":"adhemerval.zanella@linaro.org"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/glibc/patch/20260429200041.3051117-1-adhemerval.zanella@linaro.org/mbox/","series":[{"id":502143,"url":"http://patchwork.ozlabs.org/api/1.1/series/502143/?format=json","web_url":"http://patchwork.ozlabs.org/project/glibc/list/?series=502143","date":"2026-04-29T20:00:10","name":"[RFC] posix: Make system, popen, and wordexp try pidfd_spawn first (BZ 34001)","version":1,"mbox":"http://patchwork.ozlabs.org/series/502143/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2230514/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2230514/checks/","tags":{},"headers":{"Return-Path":"<libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org>","X-Original-To":["incoming@patchwork.ozlabs.org","libc-alpha@sourceware.org"],"Delivered-To":["patchwork-incoming@legolas.ozlabs.org","libc-alpha@sourceware.org"],"Authentication-Results":["legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256\n header.s=google header.b=ARqW6or+;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org\n (client-ip=2620:52:6:3111::32; helo=vm01.sourceware.org;\n envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org;\n receiver=patchwork.ozlabs.org)","sourceware.org;\n\tdkim=pass (2048-bit key,\n unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256\n header.s=google header.b=ARqW6or+","sourceware.org;\n dmarc=pass (p=none dis=none) header.from=linaro.org","sourceware.org; spf=pass smtp.mailfrom=linaro.org","server2.sourceware.org;\n arc=none smtp.remote-ip=2607:f8b0:4864:20::1130"],"Received":["from vm01.sourceware.org (vm01.sourceware.org\n [IPv6:2620:52:6:3111::32])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g5SqX2hxHz1yHX\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 30 Apr 2026 06:01:28 +1000 (AEST)","from vm01.sourceware.org (localhost [127.0.0.1])\n\tby sourceware.org (Postfix) with ESMTP id 68B3B4BB24F0\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 29 Apr 2026 20:01:25 +0000 (GMT)","from mail-yw1-x1130.google.com (mail-yw1-x1130.google.com\n [IPv6:2607:f8b0:4864:20::1130])\n by sourceware.org (Postfix) with ESMTPS id EA7504BBCD89\n for <libc-alpha@sourceware.org>; Wed, 29 Apr 2026 20:00:56 +0000 (GMT)","by mail-yw1-x1130.google.com with SMTP id\n 00721157ae682-7bb0d18c7f9so1876307b3.0\n for <libc-alpha@sourceware.org>; Wed, 29 Apr 2026 13:00:56 -0700 (PDT)","from mandiga.. ([2804:1b3:a7c0:44cb:86ff:9d41:bd4f:e3e])\n by smtp.gmail.com with ESMTPSA id\n 00721157ae682-7bd258a30cdsm20475987b3.23.2026.04.29.13.00.45\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Wed, 29 Apr 2026 13:00:47 -0700 (PDT)"],"DKIM-Filter":["OpenDKIM Filter v2.11.0 sourceware.org 68B3B4BB24F0","OpenDKIM Filter v2.11.0 sourceware.org EA7504BBCD89"],"DMARC-Filter":"OpenDMARC Filter v1.4.2 sourceware.org EA7504BBCD89","ARC-Filter":"OpenARC Filter v1.0.0 sourceware.org EA7504BBCD89","ARC-Seal":"i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1777492857; cv=none;\n b=i49+9fIkT0TQAE09nHmTej07cWrLBrayln/8go1DNbrjNRgtpH4BdJNRyV/CtgF4hz9fAiAIkrDt5SqCmBWaEDr1e4uWfa2VW+3obKx6TY/5YDLcC9/LLSyZDPytojyyrMAdBmYUhgDeMJE/rroN5qNaOrALDlF67/pG9iRIzRY=","ARC-Message-Signature":"i=1; a=rsa-sha256; d=sourceware.org; s=key;\n t=1777492857; c=relaxed/simple;\n bh=VbQ6aCRZkDNzjq5RFDr3/8yKkBHpYt8QuP9bM3aXZv8=;\n h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version;\n b=jfHE+I+FCxdH+cAK/EZvd4HdxmsCQo625/X6Z9rEKFHpezpEbpzxQmz036mJK2PeP8EyP/GyCJUNIl7GHFgPsoVTml8bfV/gpir+8Y2P5j4zuiuFuAUz9M97Mbg7fphPhVRuLayDSQHuhw7EW4Rpi7T5CzXX2s3kEq/Csrcw1J0=","ARC-Authentication-Results":"i=1; server2.sourceware.org","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=linaro.org; s=google; t=1777492856; x=1778097656; darn=sourceware.org;\n h=content-transfer-encoding:mime-version:message-id:date:subject:cc\n :to:from:from:to:cc:subject:date:message-id:reply-to;\n bh=vLuaoxs7ooUBoD8Qv2k/YUxLJ6d0wozZS4hbu+dXexw=;\n b=ARqW6or+psJ1EmcputA4XT7KxWeTARL9/wO+pXu8vBHcWFRR9UF0EoSzR0Qzb+hHzz\n 9dZpZHgsfoZN9Eir+bcVLlgj5N6oS5cgmSr+JAFUrQgmE1KPzjNVRliTr7jQU5ln/7uh\n FSbgqcr8rA/DCShbNngbWZ23zvVY9ciP9RkGeajhnfS0g4fMm1ggo4IMUOnVbgaW8IqB\n ZW6uZazJWFYOpzb6kptZSQP9JRC6mD5I9ZAt93s9T8vP3J6lRIYO+D8RpLXo7SB/ChfU\n NbK0KjytPdUymt5SWjbmmOOIADxeKLA7rmx6yzJnYKDhisiah811GkE5sRD/c9/BcY++\n M4zg==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1777492856; x=1778097656;\n h=content-transfer-encoding:mime-version:message-id:date:subject:cc\n :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date\n :message-id:reply-to;\n bh=vLuaoxs7ooUBoD8Qv2k/YUxLJ6d0wozZS4hbu+dXexw=;\n b=aBXRjIaDb8HszoNZXSa8ubTrOxpghl4Pl+BQOK5PqItbd7rX0HdGX1zkvyM76LmlLW\n nQ6oBgkKBgVJ06W3ucpyDK5gRApjXxqqk7JkukT4Jh7PD7ySc0Ho59i4gIQwZioTvWOL\n 4BkQvTGimusVSeN7aPcoDOXhxfK64oN2S38an2wUMNnVLwU4M19/qbV1OhKM72ioxeyM\n ymA7Doaa9BWDmWUONYm9mmEZfIN2jfuVlXI5lTDOOtazaG1G10SvBwYnsUxQOnWX69oh\n qJTYoVkOgd/HGSme8OWnyEz5FXLgx54/LfeH6pLdv/ZpZBu243F11aqY6ud3OE2VBzjN\n HfcQ==","X-Gm-Message-State":"AOJu0YwkYETU1Hk6H4udXBjgFCLVHKxTaAAp2wKALwVBI2PkasSdI5Mg\n S9c+u8gqCCpKkesffL+NO2gYUTsDgxt396FzM5aFRLE/bxO41aYw6CzyH4QdbA6NHuJYxT9ciS/\n /EzV5","X-Gm-Gg":"AeBDietPgaUbg2I03YGhUh5B4lSK4F+JX9Xz9C85h3APtwjCqwWCD+FC0lCceMKSEpv\n yUynLFST+jTaZ6JD360gUSJJosPqE7lX8p9PXkXs+lUMhux1p6u1seu139qFcFVV07zmE59yuzu\n Dtq2fIUhrkdT2VYIAuHQ/1IdudjvXRETpqaHOoh5CoSs6hnEj0pAZGLY17GLhdkSwPy0Cu05hnV\n J4oUXiITcxz8yrSRDfRZvcSm9N737N85+k6A1kc37Z2COfXgQkLkXKcZv8WTXmeF6TEnoZLvGqJ\n D1jWsdsosNwcKG6JUmwZkpCVbLwmcZKyCON404lF3BeJbWMq0flvq5+OmJbGFOKtZM5+LNX78r4\n xacAL1H+5z3E2DYqUKeCt14WYYISfp8M1X1RTFeE4Gru5JPBKSmSgaBjBT/t6x7UlbbVrzrs1SG\n yX7R2QExSObYgBPFz23W+6dwno0I85D77abqCFQ1hePFGDqS8gYvkXQec=","X-Received":"by 2002:a05:690c:6c13:b0:79a:cc18:19c6 with SMTP id\n 00721157ae682-7bd52969cafmr1829807b3.34.1777492855028;\n Wed, 29 Apr 2026 13:00:55 -0700 (PDT)","From":"Adhemerval Zanella <adhemerval.zanella@linaro.org>","To":"libc-alpha@sourceware.org","Cc":"=?utf-8?q?Cristian_Rodr=C3=ADguez?= <cr@cristianrodriguez.net>","Subject":"[RFC] posix: Make system, popen,\n and wordexp try pidfd_spawn first (BZ 34001)","Date":"Wed, 29 Apr 2026 17:00:10 -0300","Message-ID":"<20260429200041.3051117-1-adhemerval.zanella@linaro.org>","X-Mailer":"git-send-email 2.43.0","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","X-BeenThere":"libc-alpha@sourceware.org","X-Mailman-Version":"2.1.30","Precedence":"list","List-Id":"Libc-alpha mailing list <libc-alpha.sourceware.org>","List-Unsubscribe":"<https://sourceware.org/mailman/options/libc-alpha>,\n <mailto:libc-alpha-request@sourceware.org?subject=unsubscribe>","List-Archive":"<https://sourceware.org/pipermail/libc-alpha/>","List-Post":"<mailto:libc-alpha@sourceware.org>","List-Help":"<mailto:libc-alpha-request@sourceware.org?subject=help>","List-Subscribe":"<https://sourceware.org/mailman/listinfo/libc-alpha>,\n <mailto:libc-alpha-request@sourceware.org?subject=subscribe>","Errors-To":"libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org"},"content":"This patch updates system, popen, and wordexp to use a new internal\nprocess management abstraction (__spawn_process_create, __spawn_process_wait,\nand __spawn_process_kill) that leverages pidfd_spawn on supported Linux\nkernels before falling back to traditional posix_spawn.\n\nThe internal process_create_id_t wraps both pid_t and int (for pidfd)\nand uses the MSB to differentiate a returned pidfd from a standard PID.\nThis avoid the need to use extra word to track which type is used and\nallows to use the interface as drop-in replacement for posix_spawn\nand waitpid.\n\nBecause pidfd_spawn allocates a new file descriptor in the calling\nprocess for the pidfd, this change alters the resource consumption semantics\nof these functions.  The __spawn_process_create handles if pidfd_spawns\nreturn EMFILE or ENFILE, but if the pidfd_spawns is successful a\nfile descriptor is consumed and it counts for the process limits.\n\nChecked on x86_64-linux-gnu and i686-linux-gnu.\n---\n include/clone_internal.h                      |   1 +\n include/spawn.h                               |  33 +++++\n libio/iopopen.c                               |  23 ++--\n posix/Makefile                                |   9 ++\n posix/tst-spawn_process-fd-exhaustion.c       | 120 +++++++++++++++++\n posix/wordexp.c                               |  39 +++---\n sysdeps/generic/spawn_process.c               |  43 +++++++\n sysdeps/posix/system.c                        |  25 ++--\n .../unix/sysv/linux/include/bits/spawn_ext.h  |  11 ++\n sysdeps/unix/sysv/linux/not-errno.h           |  11 ++\n sysdeps/unix/sysv/linux/pidfd_spawn.c         |  10 +-\n sysdeps/unix/sysv/linux/spawn_process.c       | 121 ++++++++++++++++++\n 12 files changed, 404 insertions(+), 42 deletions(-)\n create mode 100644 posix/tst-spawn_process-fd-exhaustion.c\n create mode 100644 sysdeps/generic/spawn_process.c\n create mode 100644 sysdeps/unix/sysv/linux/include/bits/spawn_ext.h\n create mode 100644 sysdeps/unix/sysv/linux/spawn_process.c","diff":"diff --git a/include/clone_internal.h b/include/clone_internal.h\nindex 567160ebb5..001797fccd 100644\n--- a/include/clone_internal.h\n+++ b/include/clone_internal.h\n@@ -1,6 +1,7 @@\n #ifndef _CLONE_INTERNAL_H\n #define _CLONE_INTERNAL_H\n \n+#include <stdbool.h>\n #include <clone3.h>\n \n /* The clone3 syscall provides a superset of the functionality of the clone\ndiff --git a/include/spawn.h b/include/spawn.h\nindex 4a0b1849da..95375337e4 100644\n--- a/include/spawn.h\n+++ b/include/spawn.h\n@@ -2,6 +2,8 @@\n #include <posix/spawn.h>\n \n # ifndef _ISOMAC\n+#  include <sys/wait.h>\n+\n __typeof (posix_spawn) __posix_spawn;\n libc_hidden_proto (__posix_spawn)\n \n@@ -35,5 +37,36 @@ __typeof (posix_spawnattr_setsigdefault) __posix_spawnattr_setsigdefault\n __typeof (posix_spawnattr_setsigmask) __posix_spawnattr_setsigmask\n   attribute_hidden;\n \n+typedef int process_create_id_t;\n+_Static_assert (sizeof (process_create_id_t) == sizeof (pid_t),\n+\t\t\"process_create_id_t must have same size as pid_t\");\n+\n+/* Create a new process using pidfd_spawn or posix_spawn as a fallback, and\n+   return an identifier that should be only be used with __spawn_process_wait.\n+   The identifier is not changed if the process creation fails and the\n+   function returns the same error code as {pidfd,posix}_spawn.  */\n+int __spawn_process_create (process_create_id_t *,\n+\t\t\t    const char *__restric__,\n+\t\t\t    const posix_spawn_file_actions_t *__restrict,\n+\t\t\t    const posix_spawnattr_t *__restrict,\n+\t\t\t    char *const [__restrict_arr],\n+\t\t\t    char *const [__restrict_arr])\n+     attribute_hidden;\n+\n+/* Wait for a process created with process_create_id_t, and return the status\n+   code as for waitpid in second argument.  The third argument is the options\n+   to be used, for instance WNOHANG.\n+\n+   It returns either the process id returned by __spawn_process_create for\n+   the case of pidfd, or the pid_t if the fallback is used.  This semantic\n+   allows to use this in place of waitpid calls.  */\n+process_create_id_t __spawn_process_wait (process_create_id_t, int *, int)\n+     attribute_hidden;\n+\n+/* Send a signal to the created process using either pidfd_send_signal or\n+   kill.  */\n+int __spawn_process_kill (process_create_id_t, int)\n+     attribute_hidden;\n+\n # endif /* !_ISOMAC  */\n #endif /* spawn.h  */\ndiff --git a/libio/iopopen.c b/libio/iopopen.c\nindex 37b6b1386b..9f39dc858f 100644\n--- a/libio/iopopen.c\n+++ b/libio/iopopen.c\n@@ -40,7 +40,7 @@ struct _IO_proc_file\n {\n   struct _IO_FILE_plus file;\n   /* Following fields must match those in class procbuf (procbuf.h) */\n-  pid_t pid;\n+  process_create_id_t procid;\n   struct _IO_proc_file *next;\n };\n typedef struct _IO_proc_file _IO_proc_file;\n@@ -106,9 +106,16 @@ spawn_process (posix_spawn_file_actions_t *fa, FILE *fp, const char *command,\n \t}\n     }\n \n-  err = __posix_spawn (&((_IO_proc_file *) fp)->pid, _PATH_BSHELL, fa, NULL,\n-\t\t       (char *const[]){ (char*) \"sh\", (char*) \"-c\", (char*) \"--\",\n-\t\t       (char *) command, NULL }, __environ);\n+  err = __spawn_process_create (&((_IO_proc_file *) fp)->procid,\n+\t\t\t\t_PATH_BSHELL,\n+\t\t\t\tfa,\n+\t\t\t\tNULL,\n+\t\t\t\t(char *const[]){ (char*) \"sh\",\n+\t\t\t\t\t\t (char*) \"-c\",\n+\t\t\t\t\t\t (char*) \"--\",\n+\t\t\t\t\t\t (char *) command,\n+\t\t\t\t\t\t NULL },\n+\t\t\t\t__environ);\n   if (err != 0)\n     return err;\n \n@@ -274,7 +281,6 @@ _IO_new_proc_close (FILE *fp)\n   /* This is not name-space clean. FIXME! */\n   int wstatus;\n   _IO_proc_file **ptr = &proc_file_chain;\n-  pid_t wait_pid;\n   int status = -1;\n \n   /* Unlink from proc_file_chain. */\n@@ -306,11 +312,12 @@ _IO_new_proc_close (FILE *fp)\n     {\n       int state;\n       __pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &state);\n-      wait_pid = __waitpid (((_IO_proc_file *) fp)->pid, &wstatus, 0);\n+      status =__spawn_process_wait (((_IO_proc_file *) fp)->procid,\n+\t\t\t\t    &wstatus, 0);\n       __pthread_setcancelstate (state, NULL);\n     }\n-  while (wait_pid == -1 && errno == EINTR);\n-  if (wait_pid == -1)\n+  while (status == -1 && errno == EINTR);\n+  if (status == -1)\n     return -1;\n   return wstatus;\n }\ndiff --git a/posix/Makefile b/posix/Makefile\nindex 0fa532396f..af5a5d9960 100644\n--- a/posix/Makefile\n+++ b/posix/Makefile\n@@ -155,6 +155,7 @@ routines := \\\n   spawn_faction_addtcsetpgrp_np \\\n   spawn_faction_destroy \\\n   spawn_faction_init \\\n+  spawn_process \\\n   spawn_valid_fd \\\n   spawnattr_destroy \\\n   spawnattr_getdefault \\\n@@ -350,7 +351,12 @@ tests += \\\n   # tests\n endif\n \n+tests-static-internal := \\\n+  tst-spawn_process-fd-exhaustion \\\n+  # tests-static-internal\n+\n tests-internal := \\\n+  $(tests-static-internal)\\\n   bug-regex5 \\\n   bug-regex20 \\\n   bug-regex33 \\\n@@ -397,6 +403,7 @@ tests += \\\n endif\n \n tests-static = \\\n+  $(tests-static-internal) \\\n   tst-exec-static \\\n   tst-libc-message \\\n   tst-spawn-static \\\n@@ -803,3 +810,5 @@ tst-wordexp-reuse-ENV += MALLOC_TRACE=$(objpfx)tst-wordexp-reuse.mtrace \\\n $(objpfx)tst-wordexp-reuse-mem.out: $(objpfx)tst-wordexp-reuse.out\n \t$(common-objpfx)malloc/mtrace $(objpfx)tst-wordexp-reuse.mtrace > $@; \\\n \t$(evaluate-test)\n+\n+CFLAGS-tst-spawn_process-fd-exhaustion.c += -DOBJPFX=\\\"$(objpfx)\\\"\ndiff --git a/posix/tst-spawn_process-fd-exhaustion.c b/posix/tst-spawn_process-fd-exhaustion.c\nnew file mode 100644\nindex 0000000000..98c1fea515\n--- /dev/null\n+++ b/posix/tst-spawn_process-fd-exhaustion.c\n@@ -0,0 +1,120 @@\n+/* Check if __spawn_process_create works if there is no available\n+   file descriptor.\n+   Copyright (C) 2026 Free Software Foundation, Inc.\n+   This file is part of the GNU C Library.\n+\n+   The GNU C Library is free software; you can redistribute it and/or\n+   modify it under the terms of the GNU Lesser General Public\n+   License as published by the Free Software Foundation; either\n+   version 2.1 of the License, or (at your option) any later version.\n+\n+   The GNU C Library is distributed in the hope that it will be useful,\n+   but WITHOUT ANY WARRANTY; without even the implied warranty of\n+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n+   Lesser General Public License for more details.\n+\n+   You should have received a copy of the GNU Lesser General Public\n+   License along with the GNU C Library; if not, see\n+   <https://www.gnu.org/licenses/>.  */\n+\n+#include <errno.h>\n+#include <fcntl.h>\n+#include <intprops.h>\n+#include <paths.h>\n+#include <spawn.h>\n+#include <stdlib.h>\n+#include <sys/resource.h>\n+\n+#include <support/check.h>\n+#include <support/temp_file.h>\n+#include <support/xunistd.h>\n+\n+#include <stdio.h>\n+\n+static const char pidfile[] = OBJPFX \"tst-spawn_process-fd-exhaustion.pid\";\n+\n+static int\n+do_test (void)\n+{\n+  struct rlimit rl;\n+  int max_fd = 24;\n+\n+  if (getrlimit (RLIMIT_NOFILE, &rl) == -1)\n+    FAIL_EXIT1 (\"getrlimit (RLIMIT_NOFILE): %m\");\n+\n+  max_fd = (rl.rlim_cur < max_fd ? rl.rlim_cur : max_fd);\n+  rl.rlim_cur = max_fd;\n+\n+  if (setrlimit (RLIMIT_NOFILE, &rl) == -1)\n+    FAIL_EXIT1 (\"setrlimit (RLIMIT_NOFILE): %m\");\n+\n+  /* Exhauste the file descriptor limit with temporary files.  */\n+  int files[max_fd];\n+  int nfiles = 0;\n+  for (; nfiles < max_fd; nfiles++)\n+    {\n+      int fd = create_temp_file (\"tst-spawn_process-fd-exhaustion.pid.\", NULL);\n+      if (fd == -1)\n+\t{\n+\t  if (errno != EMFILE)\n+\t    FAIL_EXIT1 (\"create_temp_file: %m\");\n+\t  break;\n+\t}\n+      int flags = fcntl (fd, F_GETFD, 0);\n+      TEST_VERIFY_EXIT (flags != -1);\n+      TEST_VERIFY_EXIT (fcntl (fd, F_SETFD, flags | FD_CLOEXEC) != -1);\n+      files[nfiles] = fd;\n+    }\n+  TEST_VERIFY_EXIT (nfiles != 0);\n+\n+  process_create_id_t pid;\n+  {\n+    posix_spawn_file_actions_t fa;\n+    TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0);\n+    TEST_COMPARE (posix_spawn_file_actions_addopen (&fa, STDOUT_FILENO,\n+\t\t\t\t\t\t    pidfile,\n+\t\t\t\t\t\t    O_WRONLY| O_CREAT\n+\t\t\t\t\t\t    | O_TRUNC,\n+\t\t\t\t\t\t    0644), 0);\n+\n+    TEST_COMPARE (posix_spawn_file_actions_adddup2 (&fa, STDOUT_FILENO,\n+\t\t\t\t\t\t    STDERR_FILENO), 0);\n+    char *spawn_argv[] =\n+      {\n+\t(char *) _PATH_BSHELL,\n+\t(char *) \"-c\",\n+\t(char *) \"echo $$\",\n+\tNULL\n+      };\n+    int r = __spawn_process_create (&pid, _PATH_BSHELL, &fa, NULL,\n+\t\t\t\t    spawn_argv, NULL);\n+    TEST_COMPARE (r, 0);\n+\n+    int status;\n+    TEST_COMPARE (__spawn_process_wait (pid, &status, 0), pid);\n+    TEST_COMPARE (WIFEXITED (status), 1);\n+    TEST_COMPARE (WEXITSTATUS (status), 0);\n+  }\n+\n+  for (int i=0; i<nfiles; i++)\n+    xclose (files[i]);\n+\n+  {\n+    int pidfd = xopen (pidfile, O_RDONLY, 0);\n+\n+    char buf[INT_BUFSIZE_BOUND (pid_t)];\n+    ssize_t n = read (pidfd, buf, sizeof (buf));\n+    TEST_VERIFY (n < sizeof buf && n >= 0);\n+\n+    /* We only expect to read the PID.  */\n+    char *endp;\n+    long int rpid = strtol (buf, &endp, 10);\n+    TEST_VERIFY (*endp == '\\n' && endp != buf);\n+\n+    TEST_COMPARE (rpid, pid);\n+  }\n+\n+  return 0;\n+}\n+\n+#include <support/test-driver.c>\ndiff --git a/posix/wordexp.c b/posix/wordexp.c\nindex 4a8541add4..a4d7040612 100644\n--- a/posix/wordexp.c\n+++ b/posix/wordexp.c\n@@ -804,10 +804,11 @@ parse_arith (char **word, size_t *word_length, size_t *max_length,\n #include <malloc/dynarray-skeleton.c>\n \n /* Function called by child process in exec_comm() */\n-static pid_t\n-exec_comm_child (char *comm, int *fildes, bool showerr, bool noexec)\n+static bool\n+exec_comm_child (process_create_id_t *procid, char *comm, int *fildes,\n+\t\t bool showerr, bool noexec)\n {\n-  pid_t pid = -1;\n+  bool r = false;\n \n   /* Execute the command, or just check syntax?  */\n   const char *args[] = { _PATH_BSHELL, noexec ? \"-nc\" : \"-c\", comm, NULL };\n@@ -855,17 +856,17 @@ exec_comm_child (char *comm, int *fildes, bool showerr, bool noexec)\n \tgoto out;\n     }\n \n-  /* pid is not set if posix_spawn fails, so it keep the original value\n-     of -1.  */\n-  __posix_spawn (&pid, _PATH_BSHELL, &fa, NULL, (char *const *) args,\n-\t\t recreate_env ? strlist_begin (&newenv) : __environ);\n+  r = __spawn_process_create (procid, _PATH_BSHELL, &fa, NULL,\n+\t\t\t      (char *const *) args,\n+\t\t\t      recreate_env\n+\t\t\t      ? strlist_begin (&newenv) : __environ) == 0;\n \n   strlist_free (&newenv);\n \n out:\n   __posix_spawn_file_actions_destroy (&fa);\n \n-  return pid;\n+  return r;\n }\n \n /* Function to execute a command and retrieve the results */\n@@ -882,7 +883,7 @@ exec_comm (char *comm, char **word, size_t *word_length, size_t *max_length,\n   int status = 0;\n   size_t maxnewlines = 0;\n   char buffer[bufsize];\n-  pid_t pid;\n+  process_create_id_t pid;\n   bool noexec = false;\n \n   /* Do nothing if command substitution should not succeed.  */\n@@ -897,9 +898,8 @@ exec_comm (char *comm, char **word, size_t *word_length, size_t *max_length,\n     return WRDE_NOSPACE;\n \n  again:\n-  pid = exec_comm_child (comm, fildes, noexec ? false : flags & WRDE_SHOWERR,\n-\t\t\t noexec);\n-  if (pid < 0)\n+  if (!exec_comm_child (&pid, comm, fildes,\n+\t\t\tnoexec ? false : flags & WRDE_SHOWERR, noexec))\n     {\n       __close (fildes[0]);\n       __close (fildes[1]);\n@@ -908,7 +908,7 @@ exec_comm (char *comm, char **word, size_t *word_length, size_t *max_length,\n \n   /* If we are just testing the syntax, only wait.  */\n   if (noexec)\n-    return (TEMP_FAILURE_RETRY (__waitpid (pid, &status, 0)) == pid\n+    return (TEMP_FAILURE_RETRY (__spawn_process_wait (pid, &status, 0)) == pid\n \t    && status != 0) ? WRDE_SYNTAX : 0;\n \n   __close (fildes[1]);\n@@ -925,8 +925,10 @@ exec_comm (char *comm, char **word, size_t *word_length, size_t *max_length,\n \t      /* If read returned 0 then the process has closed its\n \t\t stdout.  Don't use WNOHANG in that case to avoid busy\n \t\t looping until the process eventually exits.  */\n-\t      if (TEMP_FAILURE_RETRY (__waitpid (pid, &status,\n-\t\t\t\t\t\t buflen == 0 ? 0 : WNOHANG))\n+\t      if (TEMP_FAILURE_RETRY (__spawn_process_wait (pid,\n+\t\t\t\t\t\t\t    &status,\n+\t\t\t\t\t\t\t    buflen == 0\n+\t\t\t\t\t\t\t    ? 0 : WNOHANG))\n \t\t  == 0)\n \t\tcontinue;\n \t      if ((buflen = TEMP_FAILURE_RETRY (__read (fildes[0], buffer,\n@@ -960,8 +962,9 @@ exec_comm (char *comm, char **word, size_t *word_length, size_t *max_length,\n \t      /* If read returned 0 then the process has closed its\n \t\t stdout.  Don't use WNOHANG in that case to avoid busy\n \t\t looping until the process eventually exits.  */\n-\t      if (TEMP_FAILURE_RETRY (__waitpid (pid, &status,\n-\t\t\t\t\t\t buflen == 0 ? 0 : WNOHANG))\n+\t      if (TEMP_FAILURE_RETRY (__spawn_process_wait (pid, &status,\n+\t\t\t\t\t\t\t    buflen == 0\n+\t\t\t\t\t\t\t    ? 0 : WNOHANG))\n \t\t  == 0)\n \t\tcontinue;\n \t      if ((buflen = TEMP_FAILURE_RETRY (__read (fildes[0], buffer,\n@@ -1094,7 +1097,7 @@ exec_comm (char *comm, char **word, size_t *word_length, size_t *max_length,\n \n no_space:\n   __kill (pid, SIGKILL);\n-  TEMP_FAILURE_RETRY (__waitpid (pid, NULL, 0));\n+  TEMP_FAILURE_RETRY (__spawn_process_wait (pid, NULL, 0));\n   __close (fildes[0]);\n   return WRDE_NOSPACE;\n }\ndiff --git a/sysdeps/generic/spawn_process.c b/sysdeps/generic/spawn_process.c\nnew file mode 100644\nindex 0000000000..5ca8d6260d\n--- /dev/null\n+++ b/sysdeps/generic/spawn_process.c\n@@ -0,0 +1,43 @@\n+/* Internal implementation of __spawn_process*.  Generic implementation.\n+   Copyright (C) 2026 Free Software Foundation, Inc.\n+   This file is part of the GNU C Library.\n+\n+   The GNU C Library is free software; you can redistribute it and/or\n+   modify it under the terms of the GNU Lesser General Public\n+   License as published by the Free Software Foundation; either\n+   version 2.1 of the License, or (at your option) any later version.\n+\n+   The GNU C Library is distributed in the hope that it will be useful,\n+   but WITHOUT ANY WARRANTY; without even the implied warranty of\n+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n+   Lesser General Public License for more details.\n+\n+   You should have received a copy of the GNU Lesser General Public\n+   License along with the GNU C Library; if not, see\n+   <https://www.gnu.org/licenses/>.  */\n+\n+#include <spawn.h>\n+#include <not-errno.h>\n+\n+int\n+__spawn_process_create (process_create_id_t *procid,\n+\t\t\tconst char *path,\n+\t\t\tconst posix_spawn_file_actions_t *facts,\n+\t\t\tconst posix_spawnattr_t *attr,\n+\t\t\tchar *const argv[],\n+\t\t\tchar *const envp[])\n+{\n+  return __posix_spawn (procid, path, facts, attr, argv, envp);\n+}\n+\n+int\n+__spawn_process_kill (process_create_id_t procid, int signo)\n+{\n+  return __kill (procid, signo);\n+}\n+\n+process_create_id_t\n+__spawn_process_wait (process_create_id_t procid, int *wstatus, int options)\n+{\n+  return __waitpid (procid, wstatus, options | WEXITED);\n+}\ndiff --git a/sysdeps/posix/system.c b/sysdeps/posix/system.c\nindex d01ee518ae..73fb3b3ec1 100644\n--- a/sysdeps/posix/system.c\n+++ b/sysdeps/posix/system.c\n@@ -45,7 +45,7 @@\n    last thread will restore them.\n \n    Cancellation handling is done with thread cancellation clean-up handlers\n-   on waitpid call.  */\n+   on __spawn_process_wait call.  */\n \n #ifdef _LIBC_REENTRANT\n static struct sigaction intr, quit;\n@@ -71,7 +71,7 @@ struct cancel_handler_args\n {\n   struct sigaction *quit;\n   struct sigaction *intr;\n-  pid_t pid;\n+  process_create_id_t pid;\n };\n \n static void\n@@ -79,11 +79,11 @@ cancel_handler (void *arg)\n {\n   struct cancel_handler_args *args = (struct cancel_handler_args *) (arg);\n \n-  __kill_noerrno (args->pid, SIGKILL);\n+  __spawn_process_kill (args->pid, SIGKILL);\n \n   int state;\n   __pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &state);\n-  TEMP_FAILURE_RETRY (__waitpid (args->pid, NULL, 0));\n+  TEMP_FAILURE_RETRY (__spawn_process_wait (args->pid, NULL, 0));\n   __pthread_setcancelstate (state, NULL);\n \n   DO_LOCK ();\n@@ -102,7 +102,7 @@ do_system (const char *line)\n {\n   int status = -1;\n   int ret;\n-  pid_t pid;\n+  process_create_id_t pid;\n   struct sigaction sa;\n #ifndef _LIBC_REENTRANT\n   struct sigaction intr, quit;\n@@ -144,12 +144,13 @@ do_system (const char *line)\n   __posix_spawnattr_setflags (&spawn_attr,\n \t\t\t      POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK);\n \n-  ret = __posix_spawn (&pid, SHELL_PATH, NULL, &spawn_attr,\n-\t\t       (char *const[]){ (char *) SHELL_NAME,\n-\t\t\t\t\t(char *) \"-c\",\n-\t\t\t\t\t(char *) \"--\",\n-\t\t\t\t\t(char *) line, NULL },\n-\t\t       __environ);\n+  ret = __spawn_process_create (&pid, SHELL_PATH, NULL, &spawn_attr,\n+\t\t\t\t(char *const[]){ (char *) SHELL_NAME,\n+\t\t\t\t\t\t (char *) \"-c\",\n+\t\t\t\t\t\t (char *) \"--\",\n+\t\t\t\t\t\t (char *) line,\n+\t\t\t\t\t\t NULL },\n+\t\t\t\t__environ);\n   __posix_spawnattr_destroy (&spawn_attr);\n \n   if (ret == 0)\n@@ -169,7 +170,7 @@ do_system (const char *line)\n       /* Note the system() is a cancellation point.  But since we call\n \t waitpid() which itself is a cancellation point we do not\n \t have to do anything here.  */\n-      if (TEMP_FAILURE_RETRY (__waitpid (pid, &status, 0)) != pid)\n+      if (TEMP_FAILURE_RETRY (__spawn_process_wait (pid, &status, 0)) != pid)\n \tstatus = -1;\n #if defined(_LIBC_REENTRANT) && defined(SIGCANCEL)\n       __libc_cleanup_region_end (0);\ndiff --git a/sysdeps/unix/sysv/linux/include/bits/spawn_ext.h b/sysdeps/unix/sysv/linux/include/bits/spawn_ext.h\nnew file mode 100644\nindex 0000000000..a7da319287\n--- /dev/null\n+++ b/sysdeps/unix/sysv/linux/include/bits/spawn_ext.h\n@@ -0,0 +1,11 @@\n+#ifndef _SPAWN_EXT\n+# define _SPAWN_EXT\n+\n+#include_next <bits/spawn_ext.h>\n+\n+#  ifndef _ISOMAC\n+__typeof (pidfd_spawn) __pidfd_spawn;\n+libc_hidden_proto (__pidfd_spawn);\n+# endif\n+\n+#endif\ndiff --git a/sysdeps/unix/sysv/linux/not-errno.h b/sysdeps/unix/sysv/linux/not-errno.h\nindex dc484bd3f5..00992268eb 100644\n--- a/sysdeps/unix/sysv/linux/not-errno.h\n+++ b/sysdeps/unix/sysv/linux/not-errno.h\n@@ -28,3 +28,14 @@ __kill_noerrno (pid_t pid, int sig)\n     return INTERNAL_SYSCALL_ERRNO (res);\n   return 0;\n }\n+\n+static inline int\n+__pidfd_send_signal_noerrno (int pidfd, int sig, siginfo_t *info,\n+\t\t\t     unsigned int flags)\n+{\n+  int res;\n+  res = INTERNAL_SYSCALL_CALL (pidfd_send_signal, pidfd, sig, info, flags);\n+  if (INTERNAL_SYSCALL_ERROR_P (res))\n+    return INTERNAL_SYSCALL_ERRNO (res);\n+  return 0;\n+}\ndiff --git a/sysdeps/unix/sysv/linux/pidfd_spawn.c b/sysdeps/unix/sysv/linux/pidfd_spawn.c\nindex a2e0a70017..fec1f29f89 100644\n--- a/sysdeps/unix/sysv/linux/pidfd_spawn.c\n+++ b/sysdeps/unix/sysv/linux/pidfd_spawn.c\n@@ -20,11 +20,13 @@\n #include \"spawn_int.h\"\n \n int\n-pidfd_spawn (int *pidfd, const char *path,\n-\t     const posix_spawn_file_actions_t *file_actions,\n-\t     const posix_spawnattr_t *attrp, char *const argv[],\n-\t     char *const envp[])\n+__pidfd_spawn (int *pidfd, const char *path,\n+\t       const posix_spawn_file_actions_t *file_actions,\n+\t       const posix_spawnattr_t *attrp, char *const argv[],\n+\t       char *const envp[])\n {\n   return __spawni (pidfd, path, file_actions, attrp, argv, envp,\n \t\t   SPAWN_XFLAGS_RET_PIDFD);\n }\n+libc_hidden_def (__pidfd_spawn)\n+weak_alias (__pidfd_spawn, pidfd_spawn)\ndiff --git a/sysdeps/unix/sysv/linux/spawn_process.c b/sysdeps/unix/sysv/linux/spawn_process.c\nnew file mode 100644\nindex 0000000000..c525b9f8d7\n--- /dev/null\n+++ b/sysdeps/unix/sysv/linux/spawn_process.c\n@@ -0,0 +1,121 @@\n+/* Internal implementation of __spawn_process*.  Linux implementation.\n+   Copyright (C) 2026 Free Software Foundation, Inc.\n+   This file is part of the GNU C Library.\n+\n+   The GNU C Library is free software; you can redistribute it and/or\n+   modify it under the terms of the GNU Lesser General Public\n+   License as published by the Free Software Foundation; either\n+   version 2.1 of the License, or (at your option) any later version.\n+\n+   The GNU C Library is distributed in the hope that it will be useful,\n+   but WITHOUT ANY WARRANTY; without even the implied warranty of\n+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n+   Lesser General Public License for more details.\n+\n+   You should have received a copy of the GNU Lesser General Public\n+   License along with the GNU C Library; if not, see\n+   <https://www.gnu.org/licenses/>.  */\n+\n+#include <spawn.h>\n+#include <clone_internal.h>\n+#include <not-errno.h>\n+\n+#pragma GCC optimize (\"O0\")\n+\n+int\n+__spawn_process_create (process_create_id_t *procid,\n+\t\t\tconst char *path,\n+\t\t\tconst posix_spawn_file_actions_t *facts,\n+\t\t\tconst posix_spawnattr_t *attr,\n+\t\t\tchar *const argv[],\n+\t\t\tchar *const envp[])\n+{\n+  int r;\n+\n+  if (__clone_pidfd_supported ())\n+    {\n+      int pidfd;\n+      r = __pidfd_spawn (&pidfd, path, facts, attr, argv, envp);\n+      if (r == 0)\n+\t{\n+\t  /* Both pidfd and pid_t do not allow negative values to describe a\n+\t     new process, so use the MSB to set the identifier is for\n+\t     pidfd.  */\n+\t  *procid = pidfd | 0x80000000;\n+\t  return 0;\n+\t}\n+\n+      /* Fallback to posix_spawn if file descriptor limits is reached.  */\n+      if (r != EMFILE && r != ENFILE)\n+\treturn r;\n+    }\n+\n+  pid_t pid;\n+  r = __posix_spawn (&pid, path, facts, attr, argv, envp);\n+  if (r != 0)\n+    return r;\n+  *procid = pid;\n+  return 0;\n+}\n+\n+int\n+__spawn_process_kill (process_create_id_t procid, int signo)\n+{\n+  return procid & 0x80000000\n+    ? __pidfd_send_signal_noerrno (procid & INT_MAX, signo, NULL, 0)\n+    : __kill_noerrno (procid, signo);\n+}\n+\n+process_create_id_t\n+__spawn_process_wait (process_create_id_t procid, int *wstatus, int options)\n+{\n+  bool use_pidfd = procid & 0x80000000;\n+\n+  siginfo_t info = { 0 };\n+  int waitid_opts = WEXITED;\n+  if (options & WNOHANG)\n+    waitid_opts |= WNOHANG;\n+  if (options & WUNTRACED)\n+    waitid_opts |= WSTOPPED;\n+  if (options & WCONTINUED)\n+    waitid_opts |= WCONTINUED;\n+\n+  if (__waitid (use_pidfd ? P_PIDFD : P_PID,\n+\t\tuse_pidfd ? procid & INT_MAX : procid,\n+\t\t&info,\n+\t\twaitid_opts) == -1)\n+    return -1;\n+\n+  /* Handle successful WNOHANG but without a child state change.  */\n+  if (info.si_pid == 0)\n+    return 0;\n+\n+  if (wstatus != NULL)\n+    {\n+      int status = 0;\n+      switch (info.si_code)\n+\t{\n+\tcase CLD_EXITED:\n+\t  status = (info.si_status & 0xff) << 8;\n+\t  break;\n+\tcase CLD_KILLED:\n+\t  status = info.si_status & 0x7f;\n+\t  break;\n+\tcase CLD_DUMPED:\n+\t  status = (info.si_status & 0x7f) | __WCOREFLAG;\n+\t  break;\n+\tcase CLD_STOPPED:\n+\tcase CLD_TRAPPED:\n+\t  status = ((info.si_status & 0xff) << 8) | 0x7f;\n+\t  break;\n+\tcase CLD_CONTINUED:\n+\t  status = 0xffff;\n+\t  break;\n+        }\n+      *wstatus = status;\n+    }\n+\n+  /* With P_PIDFD, waitid populates info.si_pid with the actual Process ID of\n+     the child.  */\n+  return use_pidfd ? procid : info.si_pid;\n+}\n","prefixes":["RFC"]}