Browse Source

fix: libuv patches to address child_process.spawn slowness (#33407)

* fix: libuv patches to address child_process.spawn slowness

* chore: backport additional patches

* Update .patches

Co-authored-by: Jeremy Rose <[email protected]>
Co-authored-by: deepak1556 <[email protected]>
trop[bot] 3 years ago
parent
commit
8b996a478a

+ 11 - 0
patches/node/.patches

@@ -33,3 +33,14 @@ darwin_bump_minimum_supported_version_to_10_15_3406.patch
 fix_serdes_test.patch
 fix_failing_node_js_test_on_outdated.patch
 be_compatible_with_cppgc.patch
+process_monitor_for_exit_with_kqueue_on_bsds_3441.patch
+unix_protect_fork_in_uv_spawn_from_signals.patch
+process_bsd_handle_kevent_note_exit_failure_3451.patch
+reland_macos_use_posix_spawn_instead_of_fork_3257.patch
+process_reset_the_signal_mask_if_the_fork_fails_3537.patch
+process_only_use_f_dupfd_cloexec_if_it_is_defined_3512.patch
+unix_simplify_uv_cloexec_fcntl_3492.patch
+unix_remove_uv_cloexec_ioctl_3515.patch
+process_simplify_uv_write_int_calls_3519.patch
+macos_don_t_use_thread-unsafe_strtok_3524.patch
+process_fix_hang_after_note_exit_3521.patch

+ 54 - 0
patches/node/macos_don_t_use_thread-unsafe_strtok_3524.patch

@@ -0,0 +1,54 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Ben Noordhuis <[email protected]>
+Date: Wed, 9 Mar 2022 11:06:39 +0100
+Subject: macos: don't use thread-unsafe strtok() (#3524)
+
+Refs https://github.com/libuv/libuv/pull/3524
+
+diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
+index 8cde389b826b6b167437845eccd5fed440dcadc4..147164e7ea25abbf655452930d78ee0a714cce36 100644
+--- a/deps/uv/src/unix/process.c
++++ b/deps/uv/src/unix/process.c
+@@ -387,30 +387,22 @@ static void uv__spawn_init_posix_spawn_fncs(void) {
+ 
+ 
+ static void uv__spawn_init_can_use_setsid(void) {
+-  static const int MACOS_CATALINA_VERSION_MAJOR = 19;
+-  char version_str[256];
+-  char* version_major_str;
+-  size_t version_str_size = 256;
+-  int r;
+-  int version_major;
+-
+-  /* Get a version string */
+-  r = sysctlbyname("kern.osrelease", version_str, &version_str_size, NULL, 0);
+-  if (r != 0)
++  int which[] = {CTL_KERN, KERN_OSRELEASE};
++  unsigned major;
++  unsigned minor;
++  unsigned patch;
++  char buf[256];
++  size_t len;
++
++  len = sizeof(buf);
++  if (sysctl(which, ARRAY_SIZE(which), buf, &len, NULL, 0))
+     return;
+ 
+-  /* Try to get the major version number. If not found
+-   * fall back to the fork/exec flow */
+-  version_major_str = strtok(version_str, ".");
+-  if (version_major_str == NULL)
++  /* NULL specifies to use LC_C_LOCALE */
++  if (3 != sscanf_l(buf, NULL, "%u.%u.%u", &major, &minor, &patch))
+     return;
+ 
+-  /* Parse the version major as a number. If it is greater than
+-   * the major version for macOS Catalina (aka macOS 10.15), then
+-   * the POSIX_SPAWN_SETSID flag is available */
+-  version_major = atoi_l(version_major_str, NULL); /* Use LC_C_LOCALE */
+-  if (version_major >= MACOS_CATALINA_VERSION_MAJOR)
+-    posix_spawn_can_use_setsid = 1;
++  posix_spawn_can_use_setsid = (major >= 19);  /* macOS Catalina */
+ }
+ 
+ 

+ 70 - 0
patches/node/process_bsd_handle_kevent_note_exit_failure_3451.patch

@@ -0,0 +1,70 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jameson Nash <[email protected]>
+Date: Tue, 1 Feb 2022 15:27:12 -0500
+Subject: process,bsd: handle kevent NOTE_EXIT failure (#3451)
+
+The kernel may return ESRCH if the child has already exited here.
+This is rather annoying, and means we must indirectly handle
+notification to our event loop of the process exit.
+
+Refs: https://github.com/libuv/libuv/pull/3441
+Refs: https://github.com/libuv/libuv/pull/3257
+
+diff --git a/deps/uv/src/unix/internal.h b/deps/uv/src/unix/internal.h
+index 16be13b99f5db77741aa276e90a437ef4eb5ba32..2dcc8b32f5165dd75061a1b55cc1abd2ab93ccc9 100644
+--- a/deps/uv/src/unix/internal.h
++++ b/deps/uv/src/unix/internal.h
+@@ -145,7 +145,8 @@ typedef struct uv__stream_queued_fds_s uv__stream_queued_fds_t;
+ 
+ /* loop flags */
+ enum {
+-  UV_LOOP_BLOCK_SIGPROF = 1
++  UV_LOOP_BLOCK_SIGPROF = 0x1,
++  UV_LOOP_REAP_CHILDREN = 0x2
+ };
+ 
+ /* flags of excluding ifaddr */
+diff --git a/deps/uv/src/unix/kqueue.c b/deps/uv/src/unix/kqueue.c
+index 35200f17495d80ed2d19ef9f6f76bbc92ee042f6..071fe0ce0938657d0fb840af62a432352e938a8a 100644
+--- a/deps/uv/src/unix/kqueue.c
++++ b/deps/uv/src/unix/kqueue.c
+@@ -285,7 +285,7 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
+     for (i = 0; i < nfds; i++) {
+       ev = events + i;
+       if (ev->filter == EVFILT_PROC) {
+-        uv__wait_children(loop);
++        loop->flags |= UV_LOOP_REAP_CHILDREN;
+         nevents++;
+         continue;
+       }
+@@ -383,6 +383,11 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
+       nevents++;
+     }
+ 
++    if (loop->flags & UV_LOOP_REAP_CHILDREN) {
++      loop->flags &= ~UV_LOOP_REAP_CHILDREN;
++      uv__wait_children(loop);
++    }
++
+     if (reset_timeout != 0) {
+       timeout = user_timeout;
+       reset_timeout = 0;
+diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
+index c1f6bd4b0076f0835caf83c45a6a896e7ae5def9..2920b942962357827bae9bcad23af8333e8b007f 100644
+--- a/deps/uv/src/unix/process.c
++++ b/deps/uv/src/unix/process.c
+@@ -507,8 +507,12 @@ int uv_spawn(uv_loop_t* loop,
+ #if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+     struct kevent event;
+     EV_SET(&event, pid, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, 0);
+-    if (kevent(loop->backend_fd, &event, 1, NULL, 0, NULL))
+-      abort();
++    if (kevent(loop->backend_fd, &event, 1, NULL, 0, NULL)) {
++      if (errno != ESRCH)
++        abort();
++      /* Process already exited. Call waitpid on the next loop iteration. */
++      loop->flags |= UV_LOOP_REAP_CHILDREN;
++    }
+ #endif
+ 
+     QUEUE_INSERT_TAIL(&loop->process_handles, &process->queue);

+ 158 - 0
patches/node/process_fix_hang_after_note_exit_3521.patch

@@ -0,0 +1,158 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jameson Nash <[email protected]>
+Date: Wed, 23 Mar 2022 15:39:38 +0900
+Subject: process: fix hang after NOTE_EXIT (#3521)
+
+Bug #3504 seems to affect more platforms than just OpenBSD. As this
+seems to be a race condition in these kernels, we do not want to fail
+because of it. Instead, we remove the WNOHANG flag from waitpid, and
+track exactly which processes have exited. Should also be a slight speed
+improvement for excessively large numbers of live children.
+
+diff --git a/deps/uv/src/unix/kqueue.c b/deps/uv/src/unix/kqueue.c
+index 071fe0ce0938657d0fb840af62a432352e938a8a..4c4d990ff5fa6c8ab937be2e4f79ccdaf90670c2 100644
+--- a/deps/uv/src/unix/kqueue.c
++++ b/deps/uv/src/unix/kqueue.c
+@@ -117,6 +117,7 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
+   unsigned int revents;
+   QUEUE* q;
+   uv__io_t* w;
++  uv_process_t* process;
+   sigset_t* pset;
+   sigset_t set;
+   uint64_t base;
+@@ -284,12 +285,22 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
+     loop->watchers[loop->nwatchers + 1] = (void*) (uintptr_t) nfds;
+     for (i = 0; i < nfds; i++) {
+       ev = events + i;
++      fd = ev->ident;
++
++      /* Handle kevent NOTE_EXIT results */
+       if (ev->filter == EVFILT_PROC) {
+-        loop->flags |= UV_LOOP_REAP_CHILDREN;
++        QUEUE_FOREACH(q, &loop->process_handles) {
++          process = QUEUE_DATA(q, uv_process_t, queue);
++          if (process->pid == fd) {
++            process->flags |= UV_HANDLE_REAP;
++            loop->flags |= UV_LOOP_REAP_CHILDREN;
++            break;
++          }
++        }
+         nevents++;
+         continue;
+       }
+-      fd = ev->ident;
++
+       /* Skip invalidated events, see uv__platform_invalidate_fd */
+       if (fd == -1)
+         continue;
+diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
+index 147164e7ea25abbf655452930d78ee0a714cce36..c8816b85b7e531648064e739fb89257565ad64bb 100644
+--- a/deps/uv/src/unix/process.c
++++ b/deps/uv/src/unix/process.c
+@@ -63,12 +63,18 @@ extern char **environ;
+ # include "zos-base.h"
+ #endif
+ 
+-#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
++#if defined(__APPLE__) || \
++    defined(__DragonFly__) || \
++    defined(__FreeBSD__) || \
++    defined(__NetBSD__) || \
++    defined(__OpenBSD__)
+ #include <sys/event.h>
++#else
++#define UV_USE_SIGCHLD
+ #endif
+ 
+ 
+-#if !(defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__))
++#ifdef UV_USE_SIGCHLD
+ static void uv__chld(uv_signal_t* handle, int signum) {
+   assert(signum == SIGCHLD);
+   uv__wait_children(handle->loop);
+@@ -80,6 +86,7 @@ void uv__wait_children(uv_loop_t* loop) {
+   int exit_status;
+   int term_signal;
+   int status;
++  int options;
+   pid_t pid;
+   QUEUE pending;
+   QUEUE* q;
+@@ -93,19 +100,33 @@ void uv__wait_children(uv_loop_t* loop) {
+     process = QUEUE_DATA(q, uv_process_t, queue);
+     q = QUEUE_NEXT(q);
+ 
++#ifndef UV_USE_SIGCHLD
++    if ((process->flags & UV_HANDLE_REAP) == 0)
++      continue;
++    options = 0;
++    process->flags &= ~UV_HANDLE_REAP;
++#else
++    options = WNOHANG;
++#endif
++
+     do
+-      pid = waitpid(process->pid, &status, WNOHANG);
++      pid = waitpid(process->pid, &status, options);
+     while (pid == -1 && errno == EINTR);
+ 
+-    if (pid == 0)
++#ifdef UV_USE_SIGCHLD
++    if (pid == 0) /* Not yet exited */
+       continue;
++#endif
+ 
+     if (pid == -1) {
+       if (errno != ECHILD)
+         abort();
++      /* The child died, and we missed it. This probably means someone else
++       * stole the waitpid from us. Handle this by not handling it at all. */
+       continue;
+     }
+ 
++    assert(pid == process->pid);
+     process->status = status;
+     QUEUE_REMOVE(&process->queue);
+     QUEUE_INSERT_TAIL(&pending, &process->queue);
+@@ -964,7 +985,7 @@ int uv_spawn(uv_loop_t* loop,
+       goto error;
+   }
+ 
+-#if !(defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__))
++#ifdef UV_USE_SIGCHLD
+   uv_signal_start(&loop->child_watcher, uv__chld, SIGCHLD);
+ #endif
+ 
+@@ -983,13 +1004,14 @@ int uv_spawn(uv_loop_t* loop,
+    * fail to open a stdio handle. This ensures we can eventually reap the child
+    * with waitpid. */
+   if (exec_errorno == 0) {
+-#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
++#ifndef UV_USE_SIGCHLD
+     struct kevent event;
+     EV_SET(&event, pid, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, 0);
+     if (kevent(loop->backend_fd, &event, 1, NULL, 0, NULL)) {
+       if (errno != ESRCH)
+         abort();
+       /* Process already exited. Call waitpid on the next loop iteration. */
++      process->flags |= UV_HANDLE_REAP;
+       loop->flags |= UV_LOOP_REAP_CHILDREN;
+     }
+ #endif
+diff --git a/deps/uv/src/uv-common.h b/deps/uv/src/uv-common.h
+index 8a190bf8fa8c5a282feaf251aec2a30c95776888..6001b0cf68d0b0268b578218b664a737f43c9521 100644
+--- a/deps/uv/src/uv-common.h
++++ b/deps/uv/src/uv-common.h
+@@ -130,7 +130,10 @@ enum {
+   UV_SIGNAL_ONE_SHOT                    = 0x02000000,
+ 
+   /* Only used by uv_poll_t handles. */
+-  UV_HANDLE_POLL_SLOW                   = 0x01000000
++  UV_HANDLE_POLL_SLOW                   = 0x01000000,
++
++  /* Only used by uv_process_t handles. */
++  UV_HANDLE_REAP                        = 0x10000000
+ };
+ 
+ int uv__loop_configure(uv_loop_t* loop, uv_loop_option option, va_list ap);

+ 170 - 0
patches/node/process_monitor_for_exit_with_kqueue_on_bsds_3441.patch

@@ -0,0 +1,170 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jeremy Rose <[email protected]>
+Date: Mon, 31 Jan 2022 11:49:22 -0800
+Subject: process: monitor for exit with kqueue on BSDs (#3441)
+
+This adds a workaround for an xnu kernel bug that sometimes results in
+SIGCHLD not being delivered. The workaround is to use kevent to listen
+for EVFILT_PROC/NOTE_EXIT events instead of relying on SIGCHLD on *BSD.
+
+Apple rdar: FB9529664
+Refs: https://github.com/libuv/libuv/pull/3257
+
+diff --git a/deps/uv/src/unix/internal.h b/deps/uv/src/unix/internal.h
+index 12d4da93686e993830a7d09e74d08191fc808f4f..16be13b99f5db77741aa276e90a437ef4eb5ba32 100644
+--- a/deps/uv/src/unix/internal.h
++++ b/deps/uv/src/unix/internal.h
+@@ -282,6 +282,7 @@ uv_handle_type uv__handle_type(int fd);
+ FILE* uv__open_file(const char* path);
+ int uv__getpwuid_r(uv_passwd_t* pwd);
+ int uv__search_path(const char* prog, char* buf, size_t* buflen);
++void uv__wait_children(uv_loop_t* loop);
+ 
+ /* random */
+ int uv__random_devurandom(void* buf, size_t buflen);
+diff --git a/deps/uv/src/unix/kqueue.c b/deps/uv/src/unix/kqueue.c
+index bf183d5fdc0ba89913469a294322eef84bc4cee8..35200f17495d80ed2d19ef9f6f76bbc92ee042f6 100644
+--- a/deps/uv/src/unix/kqueue.c
++++ b/deps/uv/src/unix/kqueue.c
+@@ -284,6 +284,11 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
+     loop->watchers[loop->nwatchers + 1] = (void*) (uintptr_t) nfds;
+     for (i = 0; i < nfds; i++) {
+       ev = events + i;
++      if (ev->filter == EVFILT_PROC) {
++        uv__wait_children(loop);
++        nevents++;
++        continue;
++      }
+       fd = ev->ident;
+       /* Skip invalidated events, see uv__platform_invalidate_fd */
+       if (fd == -1)
+diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
+index f4aebb0490e198cd9adcadfeb6b006de479cc993..cfcba341e0e380ecd595e4b59e39c08a7b374a48 100644
+--- a/deps/uv/src/unix/process.c
++++ b/deps/uv/src/unix/process.c
+@@ -48,10 +48,20 @@ extern char **environ;
+ # include "zos-base.h"
+ #endif
+ 
++#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
++#include <sys/event.h>
++#endif
++
+ 
++#if !(defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__))
+ static void uv__chld(uv_signal_t* handle, int signum) {
++  assert(signum == SIGCHLD);
++  uv__wait_children(handle->loop);
++}
++#endif
++
++void uv__wait_children(uv_loop_t* loop) {
+   uv_process_t* process;
+-  uv_loop_t* loop;
+   int exit_status;
+   int term_signal;
+   int status;
+@@ -60,10 +70,7 @@ static void uv__chld(uv_signal_t* handle, int signum) {
+   QUEUE* q;
+   QUEUE* h;
+ 
+-  assert(signum == SIGCHLD);
+-
+   QUEUE_INIT(&pending);
+-  loop = handle->loop;
+ 
+   h = &loop->process_handles;
+   q = QUEUE_HEAD(h);
+@@ -419,7 +426,9 @@ int uv_spawn(uv_loop_t* loop,
+   if (err)
+     goto error;
+ 
++#if !(defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__))
+   uv_signal_start(&loop->child_watcher, uv__chld, SIGCHLD);
++#endif
+ 
+   /* Acquire write lock to prevent opening new fds in worker threads */
+   uv_rwlock_wrlock(&loop->cloexec_lock);
+@@ -478,6 +487,13 @@ int uv_spawn(uv_loop_t* loop,
+ 
+   /* Only activate this handle if exec() happened successfully */
+   if (exec_errorno == 0) {
++#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
++    struct kevent event;
++    EV_SET(&event, pid, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, 0);
++    if (kevent(loop->backend_fd, &event, 1, NULL, 0, NULL))
++      abort();
++#endif
++
+     QUEUE_INSERT_TAIL(&loop->process_handles, &process->queue);
+     uv__handle_start(process);
+   }
+diff --git a/deps/uv/test/test-list.h b/deps/uv/test/test-list.h
+index 59b95da9ebe3464bd1f9ce1c534122b1f9e06636..58489c4be7b3a7b36d5b01a1f07d411ef3d99ae3 100644
+--- a/deps/uv/test/test-list.h
++++ b/deps/uv/test/test-list.h
+@@ -318,6 +318,7 @@ TEST_DECLARE   (spawn_reads_child_path)
+ TEST_DECLARE   (spawn_inherit_streams)
+ TEST_DECLARE   (spawn_quoted_path)
+ TEST_DECLARE   (spawn_tcp_server)
++TEST_DECLARE   (spawn_exercise_sigchld_issue)
+ TEST_DECLARE   (fs_poll)
+ TEST_DECLARE   (fs_poll_getpath)
+ TEST_DECLARE   (fs_poll_close_request)
+@@ -944,6 +945,7 @@ TASK_LIST_START
+   TEST_ENTRY  (spawn_inherit_streams)
+   TEST_ENTRY  (spawn_quoted_path)
+   TEST_ENTRY  (spawn_tcp_server)
++  TEST_ENTRY  (spawn_exercise_sigchld_issue)
+   TEST_ENTRY  (fs_poll)
+   TEST_ENTRY  (fs_poll_getpath)
+   TEST_ENTRY  (fs_poll_close_request)
+diff --git a/deps/uv/test/test-spawn.c b/deps/uv/test/test-spawn.c
+index 9f2eb24b2d6daf339bec12027134409cfc3b4a82..dfd5458ef37c664af9a55a8383bdb3121885db3b 100644
+--- a/deps/uv/test/test-spawn.c
++++ b/deps/uv/test/test-spawn.c
+@@ -1891,6 +1891,44 @@ TEST_IMPL(spawn_quoted_path) {
+ #endif
+ }
+ 
++TEST_IMPL(spawn_exercise_sigchld_issue) {
++  int r;
++  int i;
++  uv_process_options_t dummy_options = {0};
++  uv_process_t dummy_processes[100];
++  char* args[2];
++
++  init_process_options("spawn_helper1", exit_cb);
++
++  r = uv_spawn(uv_default_loop(), &process, &options);
++  ASSERT_EQ(r, 0);
++
++  // This test exercises a bug in the darwin kernel that causes SIGCHLD not to
++  // be delivered sometimes. Calling posix_spawn many times increases the
++  // likelihood of encountering this issue, so spin a few times to make this
++  // test more reliable.
++  dummy_options.file = args[0] = "program-that-had-better-not-exist";
++  args[1] = NULL;
++  dummy_options.args = args;
++  dummy_options.exit_cb = fail_cb;
++  dummy_options.flags = 0;
++  for (i = 0; i < 100; i++) {
++    r = uv_spawn(uv_default_loop(), &dummy_processes[i], &dummy_options);
++    if (r != UV_ENOENT)
++      ASSERT_EQ(r, UV_EACCES);
++    uv_close((uv_handle_t*) &dummy_processes[i], close_cb);
++  }
++
++  r = uv_run(uv_default_loop(), UV_RUN_DEFAULT);
++  ASSERT_EQ(r, 0);
++
++  ASSERT_EQ(exit_cb_called, 1);
++  ASSERT_EQ(close_cb_called, 101);
++
++  MAKE_VALGRIND_HAPPY();
++  return 0;
++}
++
+ /* Helper for child process of spawn_inherit_streams */
+ #ifndef _WIN32
+ void spawn_stdin_stdout(void) {

+ 54 - 0
patches/node/process_only_use_f_dupfd_cloexec_if_it_is_defined_3512.patch

@@ -0,0 +1,54 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jameson Nash <[email protected]>
+Date: Sat, 5 Mar 2022 12:52:04 -0500
+Subject: process: only use F_DUPFD_CLOEXEC if it is defined (#3512)
+
+We can save a syscall on most modern systems (required by POSIX 2008),
+but not on all systems.
+
+Also handle errors from CLOEXEC. Even though fcntl does not really
+define there to be any, it could theoretically be EBADF if the user
+happened to pass a bad file descriptor to the same number fd (such that
+no other code happened to already fail on that).
+
+diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
+index d208f99be40df9f36447552daf2772c1cab1ce79..7705068730cb0536998bad7d304cb87df99b72e8 100644
+--- a/deps/uv/src/unix/process.c
++++ b/deps/uv/src/unix/process.c
+@@ -275,9 +275,20 @@ static void uv__process_child_init(const uv_process_options_t* options,
+     use_fd = pipes[fd][1];
+     if (use_fd < 0 || use_fd >= fd)
+       continue;
++#ifdef F_DUPFD_CLOEXEC /* POSIX 2008 */
+     pipes[fd][1] = fcntl(use_fd, F_DUPFD_CLOEXEC, stdio_count);
++#else
++    pipes[fd][1] = fcntl(use_fd, F_DUPFD, stdio_count);
++#endif
+     if (pipes[fd][1] == -1)
+       uv__write_errno(error_fd);
++#ifndef F_DUPFD_CLOEXEC /* POSIX 2008 */
++    n = uv__cloexec_fcntl(pipes[fd][1], 1);
++    if (n) {
++      uv__write_int(error_fd, n);
++      _exit(127);
++    }
++#endif
+   }
+ 
+   for (fd = 0; fd < stdio_count; fd++) {
+@@ -300,8 +311,13 @@ static void uv__process_child_init(const uv_process_options_t* options,
+     }
+ 
+     if (fd == use_fd) {
+-      if (close_fd == -1)
+-        uv__cloexec_fcntl(use_fd, 0);
++      if (close_fd == -1) {
++        n = uv__cloexec_fcntl(use_fd, 0);
++        if (n) {
++          uv__write_int(error_fd, n);
++          _exit(127);
++        }
++      }
+     }
+     else {
+       fd = dup2(use_fd, fd);

+ 36 - 0
patches/node/process_reset_the_signal_mask_if_the_fork_fails_3537.patch

@@ -0,0 +1,36 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jameson Nash <[email protected]>
+Date: Fri, 11 Mar 2022 12:05:24 -0500
+Subject: process: reset the signal mask if the fork fails (#3537)
+
+Fix a regression that sneaked into posix spawn changes.
+
+Refs: https://github.com/libuv/libuv/pull/3257
+
+diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
+index b85aa3b94edd040952e0d350a47a38d9ba8a67d3..d208f99be40df9f36447552daf2772c1cab1ce79 100644
+--- a/deps/uv/src/unix/process.c
++++ b/deps/uv/src/unix/process.c
+@@ -790,11 +790,6 @@ static int uv__spawn_and_init_child_fork(const uv_process_options_t* options,
+ 
+   *pid = fork();
+ 
+-  if (*pid == -1) {
+-    /* Failed to fork */
+-    return UV__ERR(errno);
+-  }
+-
+   if (*pid == 0) {
+     /* Fork succeeded, in the child process */
+     uv__process_child_init(options, stdio_count, pipes, error_fd);
+@@ -804,6 +799,10 @@ static int uv__spawn_and_init_child_fork(const uv_process_options_t* options,
+   if (pthread_sigmask(SIG_SETMASK, &sigoldset, NULL) != 0)
+     abort();
+ 
++  if (*pid == -1)
++    /* Failed to fork */
++    return UV__ERR(errno);
++
+   /* Fork succeeded, in the parent process */
+   return 0;
+ }

+ 63 - 0
patches/node/process_simplify_uv_write_int_calls_3519.patch

@@ -0,0 +1,63 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jameson Nash <[email protected]>
+Date: Mon, 7 Mar 2022 17:07:49 -0500
+Subject: process: simplify uv__write_int calls (#3519)
+
+Refs https://github.com/libuv/libuv/pull/3519
+
+diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
+index b6f9756c6a6710f5f10762b9299cc35047b98097..8cde389b826b6b167437845eccd5fed440dcadc4 100644
+--- a/deps/uv/src/unix/process.c
++++ b/deps/uv/src/unix/process.c
+@@ -216,16 +216,14 @@ static void uv__write_int(int fd, int val) {
+     n = write(fd, &val, sizeof(val));
+   while (n == -1 && errno == EINTR);
+ 
+-  if (n == -1 && errno == EPIPE)
+-    return; /* parent process has quit */
+-
+-  assert(n == sizeof(val));
++  /* The write might have failed (e.g. if the parent process has died),
++   * but we have nothing left but to _exit ourself now too. */
++  _exit(127);
+ }
+ 
+ 
+ static void uv__write_errno(int error_fd) {
+   uv__write_int(error_fd, UV__ERR(errno));
+-  _exit(127);
+ }
+ 
+ 
+@@ -284,10 +282,8 @@ static void uv__process_child_init(const uv_process_options_t* options,
+       uv__write_errno(error_fd);
+ #ifndef F_DUPFD_CLOEXEC /* POSIX 2008 */
+     n = uv__cloexec(pipes[fd][1], 1);
+-    if (n) {
++    if (n)
+       uv__write_int(error_fd, n);
+-      _exit(127);
+-    }
+ #endif
+   }
+ 
+@@ -313,10 +309,8 @@ static void uv__process_child_init(const uv_process_options_t* options,
+     if (fd == use_fd) {
+       if (close_fd == -1) {
+         n = uv__cloexec(use_fd, 0);
+-        if (n) {
++        if (n)
+           uv__write_int(error_fd, n);
+-          _exit(127);
+-        }
+       }
+     }
+     else {
+@@ -368,7 +362,6 @@ static void uv__process_child_init(const uv_process_options_t* options,
+ #endif
+ 
+   uv__write_errno(error_fd);
+-  abort();
+ }
+ #endif
+ 

+ 889 - 0
patches/node/reland_macos_use_posix_spawn_instead_of_fork_3257.patch

@@ -0,0 +1,889 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jameson Nash <[email protected]>
+Date: Wed, 2 Mar 2022 15:15:39 -0500
+Subject: Reland "macos: use posix_spawn instead of fork" (#3257)
+
+Fixes: https://github.com/libuv/libuv/issues/3050
+Refs: https://github.com/libuv/libuv/issues/3086
+Refs: https://github.com/libuv/libuv/pull/3064
+Refs: https://github.com/libuv/libuv/pull/3107
+Refs: https://github.com/libuv/libuv/pull/3064
+
+This reverts commit 217fdf4265589889d00c7c0622fde2710971a020, then fixes
+several issues with it:
+
+* remove error fast-cleanup code that triggers a nodejs bug
+
+Refs: https://github.com/libuv/libuv/pull/3107#issuecomment-782482608
+
+* protect posix_spawn from EINTR
+
+This is not a documented valid error, but seems to have been observed.
+
+* ignore setuid/setgid syscall
+
+This kernel function is not permitted unless the process is setuid root,
+so disable this syscall. Falling back to fork/exec should be okay for
+the rare cases that the user decides they need to do setuid(getuid()) or
+setuid(geteuid()) for the child.
+
+Refs: https://github.com/libuv/libuv/pull/3107#issuecomment-782482608
+
+* improve posix_spawn path search
+
+Ports the improvements in musl back to this function
+
+* fix some additional problems and formatting issues
+
+We previously might fail to start a watcher, in rare failure cases,
+resulting in a zombie that we would fail to kill. Also avoid creating
+the signal-pipe unless required (addresses a review comment from Apple)
+
+* fix fd->fd mapping reuse
+
+There was a chance that when duplicating the fd's into stdio_count+fd we
+might be closing a currently opened fd with that value.
+
+diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
+index 2920b942962357827bae9bcad23af8333e8b007f..b85aa3b94edd040952e0d350a47a38d9ba8a67d3 100644
+--- a/deps/uv/src/unix/process.c
++++ b/deps/uv/src/unix/process.c
+@@ -27,6 +27,7 @@
+ #include <assert.h>
+ #include <errno.h>
+ #include <signal.h>
++#include <string.h>
+ 
+ #include <sys/types.h>
+ #include <sys/wait.h>
+@@ -35,8 +36,21 @@
+ #include <poll.h>
+ 
+ #if defined(__APPLE__) && !TARGET_OS_IPHONE
++# include <spawn.h>
++# include <paths.h>
++# include <sys/kauth.h>
++# include <sys/types.h>
++# include <sys/sysctl.h>
++# include <dlfcn.h>
+ # include <crt_externs.h>
++# include <xlocale.h>
+ # define environ (*_NSGetEnviron())
++
++/* macOS 10.14 back does not define this constant */
++# ifndef POSIX_SPAWN_SETSID
++#  define POSIX_SPAWN_SETSID 1024
++# endif
++
+ #else
+ extern char **environ;
+ #endif
+@@ -261,22 +275,22 @@ static void uv__process_child_init(const uv_process_options_t* options,
+     use_fd = pipes[fd][1];
+     if (use_fd < 0 || use_fd >= fd)
+       continue;
+-    pipes[fd][1] = fcntl(use_fd, F_DUPFD, stdio_count);
++    pipes[fd][1] = fcntl(use_fd, F_DUPFD_CLOEXEC, stdio_count);
+     if (pipes[fd][1] == -1)
+       uv__write_errno(error_fd);
+   }
+ 
+   for (fd = 0; fd < stdio_count; fd++) {
+-    close_fd = pipes[fd][0];
++    close_fd = -1;
+     use_fd = pipes[fd][1];
+ 
+     if (use_fd < 0) {
+       if (fd >= 3)
+         continue;
+       else {
+-        /* redirect stdin, stdout and stderr to /dev/null even if UV_IGNORE is
+-         * set
+-         */
++        /* Redirect stdin, stdout and stderr to /dev/null even if UV_IGNORE is
++         * set. */
++        uv__close_nocheckstdio(fd); /* Free up fd, if it happens to be open. */
+         use_fd = open("/dev/null", fd == 0 ? O_RDONLY : O_RDWR);
+         close_fd = use_fd;
+ 
+@@ -285,28 +299,24 @@ static void uv__process_child_init(const uv_process_options_t* options,
+       }
+     }
+ 
+-    if (fd == use_fd)
+-      uv__cloexec_fcntl(use_fd, 0);
+-    else
++    if (fd == use_fd) {
++      if (close_fd == -1)
++        uv__cloexec_fcntl(use_fd, 0);
++    }
++    else {
+       fd = dup2(use_fd, fd);
++    }
+ 
+     if (fd == -1)
+       uv__write_errno(error_fd);
+ 
+-    if (fd <= 2)
++    if (fd <= 2 && close_fd == -1)
+       uv__nonblock_fcntl(fd, 0);
+ 
+     if (close_fd >= stdio_count)
+       uv__close(close_fd);
+   }
+ 
+-  for (fd = 0; fd < stdio_count; fd++) {
+-    use_fd = pipes[fd][1];
+-
+-    if (use_fd >= stdio_count)
+-      uv__close(use_fd);
+-  }
+-
+   if (options->cwd != NULL && chdir(options->cwd))
+     uv__write_errno(error_fd);
+ 
+@@ -327,9 +337,8 @@ static void uv__process_child_init(const uv_process_options_t* options,
+   if ((options->flags & UV_PROCESS_SETUID) && setuid(options->uid))
+     uv__write_errno(error_fd);
+ 
+-  if (options->env != NULL) {
++  if (options->env != NULL)
+     environ = options->env;
+-  }
+ 
+   /* Reset signal mask just before exec. */
+   sigemptyset(&signewset);
+@@ -348,6 +357,562 @@ static void uv__process_child_init(const uv_process_options_t* options,
+ #endif
+ 
+ 
++#if defined(__APPLE__)
++typedef struct uv__posix_spawn_fncs_tag {
++  struct {
++    int (*addchdir_np)(const posix_spawn_file_actions_t *, const char *);
++  } file_actions;
++} uv__posix_spawn_fncs_t;
++
++
++static uv_once_t posix_spawn_init_once = UV_ONCE_INIT;
++static uv__posix_spawn_fncs_t posix_spawn_fncs;
++static int posix_spawn_can_use_setsid;
++
++
++static void uv__spawn_init_posix_spawn_fncs(void) {
++  /* Try to locate all non-portable functions at runtime */
++  posix_spawn_fncs.file_actions.addchdir_np =
++    dlsym(RTLD_DEFAULT, "posix_spawn_file_actions_addchdir_np");
++}
++
++
++static void uv__spawn_init_can_use_setsid(void) {
++  static const int MACOS_CATALINA_VERSION_MAJOR = 19;
++  char version_str[256];
++  char* version_major_str;
++  size_t version_str_size = 256;
++  int r;
++  int version_major;
++
++  /* Get a version string */
++  r = sysctlbyname("kern.osrelease", version_str, &version_str_size, NULL, 0);
++  if (r != 0)
++    return;
++
++  /* Try to get the major version number. If not found
++   * fall back to the fork/exec flow */
++  version_major_str = strtok(version_str, ".");
++  if (version_major_str == NULL)
++    return;
++
++  /* Parse the version major as a number. If it is greater than
++   * the major version for macOS Catalina (aka macOS 10.15), then
++   * the POSIX_SPAWN_SETSID flag is available */
++  version_major = atoi_l(version_major_str, NULL); /* Use LC_C_LOCALE */
++  if (version_major >= MACOS_CATALINA_VERSION_MAJOR)
++    posix_spawn_can_use_setsid = 1;
++}
++
++
++static void uv__spawn_init_posix_spawn(void) {
++  /* Init handles to all potentially non-defined functions */
++  uv__spawn_init_posix_spawn_fncs();
++
++  /* Init feature detection for POSIX_SPAWN_SETSID flag */
++  uv__spawn_init_can_use_setsid();
++}
++
++
++static int uv__spawn_set_posix_spawn_attrs(
++    posix_spawnattr_t* attrs,
++    const uv__posix_spawn_fncs_t* posix_spawn_fncs,
++    const uv_process_options_t* options) {
++  int err;
++  unsigned int flags;
++  sigset_t signal_set;
++
++  err = posix_spawnattr_init(attrs);
++  if (err != 0) {
++    /* If initialization fails, no need to de-init, just return */
++    return err;
++  }
++
++  if (options->flags & (UV_PROCESS_SETUID | UV_PROCESS_SETGID)) {
++    /* kauth_cred_issuser currently requires exactly uid == 0 for these
++     * posixspawn_attrs (set_groups_np, setuid_np, setgid_np), which deviates
++     * from the normal specification of setuid (which also uses euid), and they
++     * are also undocumented syscalls, so we do not use them. */
++    err = ENOSYS;
++    goto error;
++  }
++
++  /* Set flags for spawn behavior
++   * 1) POSIX_SPAWN_CLOEXEC_DEFAULT: (Apple Extension) All descriptors in the
++   *    parent will be treated as if they had been created with O_CLOEXEC. The
++   *    only fds that will be passed on to the child are those manipulated by
++   *    the file actions
++   * 2) POSIX_SPAWN_SETSIGDEF: Signals mentioned in spawn-sigdefault in the
++   *    spawn attributes will be reset to behave as their default
++   * 3) POSIX_SPAWN_SETSIGMASK: Signal mask will be set to the value of
++   *    spawn-sigmask in attributes
++   * 4) POSIX_SPAWN_SETSID: Make the process a new session leader if a detached
++   *    session was requested. */
++  flags = POSIX_SPAWN_CLOEXEC_DEFAULT |
++          POSIX_SPAWN_SETSIGDEF |
++          POSIX_SPAWN_SETSIGMASK;
++  if (options->flags & UV_PROCESS_DETACHED) {
++    /* If running on a version of macOS where this flag is not supported,
++     * revert back to the fork/exec flow. Otherwise posix_spawn will
++     * silently ignore the flag. */
++    if (!posix_spawn_can_use_setsid) {
++      err = ENOSYS;
++      goto error;
++    }
++
++    flags |= POSIX_SPAWN_SETSID;
++  }
++  err = posix_spawnattr_setflags(attrs, flags);
++  if (err != 0)
++    goto error;
++
++  /* Reset all signal the child to their default behavior */
++  sigfillset(&signal_set);
++  err = posix_spawnattr_setsigdefault(attrs, &signal_set);
++  if (err != 0)
++    goto error;
++
++  /* Reset the signal mask for all signals */
++  sigemptyset(&signal_set);
++  err = posix_spawnattr_setsigmask(attrs, &signal_set);
++  if (err != 0)
++    goto error;
++
++  return err;
++
++error:
++  (void) posix_spawnattr_destroy(attrs);
++  return err;
++}
++
++
++static int uv__spawn_set_posix_spawn_file_actions(
++    posix_spawn_file_actions_t* actions,
++    const uv__posix_spawn_fncs_t* posix_spawn_fncs,
++    const uv_process_options_t* options,
++    int stdio_count,
++    int (*pipes)[2]) {
++  int fd;
++  int fd2;
++  int use_fd;
++  int err;
++
++  err = posix_spawn_file_actions_init(actions);
++  if (err != 0) {
++    /* If initialization fails, no need to de-init, just return */
++    return err;
++  }
++
++  /* Set the current working directory if requested */
++  if (options->cwd != NULL) {
++    if (posix_spawn_fncs->file_actions.addchdir_np == NULL) {
++      err = ENOSYS;
++      goto error;
++    }
++
++    err = posix_spawn_fncs->file_actions.addchdir_np(actions, options->cwd);
++    if (err != 0)
++      goto error;
++  }
++
++  /* Do not return ENOSYS after this point, as we may mutate pipes. */
++
++  /* First duplicate low numbered fds, since it's not safe to duplicate them,
++   * they could get replaced. Example: swapping stdout and stderr; without
++   * this fd 2 (stderr) would be duplicated into fd 1, thus making both
++   * stdout and stderr go to the same fd, which was not the intention. */
++  for (fd = 0; fd < stdio_count; fd++) {
++    use_fd = pipes[fd][1];
++    if (use_fd < 0 || use_fd >= fd)
++      continue;
++    use_fd = stdio_count;
++    for (fd2 = 0; fd2 < stdio_count; fd2++) {
++      /* If we were not setting POSIX_SPAWN_CLOEXEC_DEFAULT, we would need to
++       * also consider whether fcntl(fd, F_GETFD) returned without the
++       * FD_CLOEXEC flag set. */
++      if (pipes[fd2][1] == use_fd) {
++        use_fd++;
++        fd2 = 0;
++      }
++    }
++    err = posix_spawn_file_actions_adddup2(
++      actions,
++      pipes[fd][1],
++      use_fd);
++    assert(err != ENOSYS);
++    if (err != 0)
++      goto error;
++    pipes[fd][1] = use_fd;
++  }
++
++  /* Second, move the descriptors into their respective places */
++  for (fd = 0; fd < stdio_count; fd++) {
++    use_fd = pipes[fd][1];
++    if (use_fd < 0) {
++      if (fd >= 3)
++        continue;
++      else {
++        /* If ignored, redirect to (or from) /dev/null, */
++        err = posix_spawn_file_actions_addopen(
++          actions,
++          fd,
++          "/dev/null",
++          fd == 0 ? O_RDONLY : O_RDWR,
++          0);
++        assert(err != ENOSYS);
++        if (err != 0)
++          goto error;
++        continue;
++      }
++    }
++
++    if (fd == use_fd)
++        err = posix_spawn_file_actions_addinherit_np(actions, fd);
++    else
++        err = posix_spawn_file_actions_adddup2(actions, use_fd, fd);
++    assert(err != ENOSYS);
++    if (err != 0)
++      goto error;
++
++    /* Make sure the fd is marked as non-blocking (state shared between child
++     * and parent). */
++    uv__nonblock_fcntl(use_fd, 0);
++  }
++
++  /* Finally, close all the superfluous descriptors */
++  for (fd = 0; fd < stdio_count; fd++) {
++    use_fd = pipes[fd][1];
++    if (use_fd < stdio_count)
++      continue;
++
++    /* Check if we already closed this. */
++    for (fd2 = 0; fd2 < fd; fd2++) {
++      if (pipes[fd2][1] == use_fd)
++          break;
++    }
++    if (fd2 < fd)
++      continue;
++
++    err = posix_spawn_file_actions_addclose(actions, use_fd);
++    assert(err != ENOSYS);
++    if (err != 0)
++      goto error;
++  }
++
++  return 0;
++
++error:
++  (void) posix_spawn_file_actions_destroy(actions);
++  return err;
++}
++
++char* uv__spawn_find_path_in_env(char** env) {
++  char** env_iterator;
++  const char path_var[] = "PATH=";
++
++  /* Look for an environment variable called PATH in the
++   * provided env array, and return its value if found */
++  for (env_iterator = env; *env_iterator != NULL; env_iterator++) {
++    if (strncmp(*env_iterator, path_var, sizeof(path_var) - 1) == 0) {
++      /* Found "PATH=" at the beginning of the string */
++      return *env_iterator + sizeof(path_var) - 1;
++    }
++  }
++
++  return NULL;
++}
++
++
++static int uv__spawn_resolve_and_spawn(const uv_process_options_t* options,
++                                       posix_spawnattr_t* attrs,
++                                       posix_spawn_file_actions_t* actions,
++                                       pid_t* pid) {
++  const char *p;
++  const char *z;
++  const char *path;
++  size_t l;
++  size_t k;
++  int err;
++  int seen_eacces;
++
++  path = NULL;
++  err = -1;
++  seen_eacces = 0;
++
++  /* Short circuit for erroneous case */
++  if (options->file == NULL)
++    return ENOENT;
++
++  /* The environment for the child process is that of the parent unless overriden
++   * by options->env */
++  char** env = environ;
++  if (options->env != NULL)
++    env = options->env;
++
++  /* If options->file contains a slash, posix_spawn/posix_spawnp behave
++   * the same, and don't involve PATH resolution at all. Otherwise, if
++   * options->file does not include a slash, but no custom environment is
++   * to be used, the environment used for path resolution as well for the
++   * child process is that of the parent process, so posix_spawnp is the
++   * way to go. */
++  if (strchr(options->file, '/') != NULL || options->env == NULL) {
++    do
++      err = posix_spawnp(pid, options->file, actions, attrs, options->args, env);
++    while (err == EINTR);
++    return err;
++  }
++
++  /* Look for the definition of PATH in the provided env */
++  path = uv__spawn_find_path_in_env(options->env);
++
++  /* The following resolution logic (execvpe emulation) is copied from
++   * https://git.musl-libc.org/cgit/musl/tree/src/process/execvp.c
++   * and adapted to work for our specific usage */
++
++  /* If no path was provided in options->env, use the default value
++   * to look for the executable */
++  if (path == NULL)
++    path = _PATH_DEFPATH;
++
++  k = strnlen(options->file, NAME_MAX + 1);
++  if (k > NAME_MAX)
++    return ENAMETOOLONG;
++
++  l = strnlen(path, PATH_MAX - 1) + 1;
++
++  for (p = path;; p = z) {
++    /* Compose the new process file from the entry in the PATH
++     * environment variable and the actual file name */
++    char b[PATH_MAX + NAME_MAX];
++    z = strchr(p, ':');
++    if (!z)
++      z = p + strlen(p);
++    if ((size_t)(z - p) >= l) {
++      if (!*z++)
++        break;
++
++      continue;
++    }
++    memcpy(b, p, z - p);
++    b[z - p] = '/';
++    memcpy(b + (z - p) + (z > p), options->file, k + 1);
++
++    /* Try to spawn the new process file. If it fails with ENOENT, the
++     * new process file is not in this PATH entry, continue with the next
++     * PATH entry. */
++    do
++      err = posix_spawn(pid, b, actions, attrs, options->args, env);
++    while (err == EINTR);
++
++    switch (err) {
++    case EACCES:
++      seen_eacces = 1;
++      break; /* continue search */
++    case ENOENT:
++    case ENOTDIR:
++      break; /* continue search */
++    default:
++      return err;
++    }
++
++    if (!*z++)
++      break;
++  }
++
++  if (seen_eacces)
++    return EACCES;
++  return err;
++}
++
++
++static int uv__spawn_and_init_child_posix_spawn(
++    const uv_process_options_t* options,
++    int stdio_count,
++    int (*pipes)[2],
++    pid_t* pid,
++    const uv__posix_spawn_fncs_t* posix_spawn_fncs) {
++  int err;
++  posix_spawnattr_t attrs;
++  posix_spawn_file_actions_t actions;
++
++  err = uv__spawn_set_posix_spawn_attrs(&attrs, posix_spawn_fncs, options);
++  if (err != 0)
++    goto error;
++
++  /* This may mutate pipes. */
++  err = uv__spawn_set_posix_spawn_file_actions(&actions,
++                                               posix_spawn_fncs,
++                                               options,
++                                               stdio_count,
++                                               pipes);
++  if (err != 0) {
++    (void) posix_spawnattr_destroy(&attrs);
++    goto error;
++  }
++
++  /* Try to spawn options->file resolving in the provided environment
++   * if any */
++  err = uv__spawn_resolve_and_spawn(options, &attrs, &actions, pid);
++  assert(err != ENOSYS);
++
++  /* Destroy the actions/attributes */
++  (void) posix_spawn_file_actions_destroy(&actions);
++  (void) posix_spawnattr_destroy(&attrs);
++
++error:
++  /* In an error situation, the attributes and file actions are
++   * already destroyed, only the happy path requires cleanup */
++  return UV__ERR(err);
++}
++#endif
++
++static int uv__spawn_and_init_child_fork(const uv_process_options_t* options,
++                                         int stdio_count,
++                                         int (*pipes)[2],
++                                         int error_fd,
++                                         pid_t* pid) {
++  sigset_t signewset;
++  sigset_t sigoldset;
++
++  /* Start the child with most signals blocked, to avoid any issues before we
++   * can reset them, but allow program failures to exit (and not hang). */
++  sigfillset(&signewset);
++  sigdelset(&signewset, SIGKILL);
++  sigdelset(&signewset, SIGSTOP);
++  sigdelset(&signewset, SIGTRAP);
++  sigdelset(&signewset, SIGSEGV);
++  sigdelset(&signewset, SIGBUS);
++  sigdelset(&signewset, SIGILL);
++  sigdelset(&signewset, SIGSYS);
++  sigdelset(&signewset, SIGABRT);
++  if (pthread_sigmask(SIG_BLOCK, &signewset, &sigoldset) != 0)
++    abort();
++
++  *pid = fork();
++
++  if (*pid == -1) {
++    /* Failed to fork */
++    return UV__ERR(errno);
++  }
++
++  if (*pid == 0) {
++    /* Fork succeeded, in the child process */
++    uv__process_child_init(options, stdio_count, pipes, error_fd);
++    abort();
++  }
++
++  if (pthread_sigmask(SIG_SETMASK, &sigoldset, NULL) != 0)
++    abort();
++
++  /* Fork succeeded, in the parent process */
++  return 0;
++}
++
++static int uv__spawn_and_init_child(
++    uv_loop_t* loop,
++    const uv_process_options_t* options,
++    int stdio_count,
++    int (*pipes)[2],
++    pid_t* pid) {
++  int signal_pipe[2] = { -1, -1 };
++  int status;
++  int err;
++  int exec_errorno;
++  ssize_t r;
++
++#if defined(__APPLE__)
++  uv_once(&posix_spawn_init_once, uv__spawn_init_posix_spawn);
++
++  /* Special child process spawn case for macOS Big Sur (11.0) onwards
++   *
++   * Big Sur introduced a significant performance degradation on a call to
++   * fork/exec when the process has many pages mmaped in with MAP_JIT, like, say
++   * a javascript interpreter. Electron-based applications, for example,
++   * are impacted; though the magnitude of the impact depends on how much the
++   * app relies on subprocesses.
++   *
++   * On macOS, though, posix_spawn is implemented in a way that does not
++   * exhibit the problem. This block implements the forking and preparation
++   * logic with posix_spawn and its related primitives. It also takes advantage of
++   * the macOS extension POSIX_SPAWN_CLOEXEC_DEFAULT that makes impossible to
++   * leak descriptors to the child process. */
++  err = uv__spawn_and_init_child_posix_spawn(options,
++                                             stdio_count,
++                                             pipes,
++                                             pid,
++                                             &posix_spawn_fncs);
++
++  /* The posix_spawn flow will return UV_ENOSYS if any of the posix_spawn_x_np
++   * non-standard functions is both _needed_ and _undefined_. In those cases,
++   * default back to the fork/execve strategy. For all other errors, just fail. */
++  if (err != UV_ENOSYS)
++    return err;
++
++#endif
++
++  /* This pipe is used by the parent to wait until
++   * the child has called `execve()`. We need this
++   * to avoid the following race condition:
++   *
++   *    if ((pid = fork()) > 0) {
++   *      kill(pid, SIGTERM);
++   *    }
++   *    else if (pid == 0) {
++   *      execve("/bin/cat", argp, envp);
++   *    }
++   *
++   * The parent sends a signal immediately after forking.
++   * Since the child may not have called `execve()` yet,
++   * there is no telling what process receives the signal,
++   * our fork or /bin/cat.
++   *
++   * To avoid ambiguity, we create a pipe with both ends
++   * marked close-on-exec. Then, after the call to `fork()`,
++   * the parent polls the read end until it EOFs or errors with EPIPE.
++   */
++  err = uv__make_pipe(signal_pipe, 0);
++  if (err)
++    return err;
++
++  /* Acquire write lock to prevent opening new fds in worker threads */
++  uv_rwlock_wrlock(&loop->cloexec_lock);
++
++  err = uv__spawn_and_init_child_fork(options, stdio_count, pipes, signal_pipe[1], pid);
++
++  /* Release lock in parent process */
++  uv_rwlock_wrunlock(&loop->cloexec_lock);
++
++  uv__close(signal_pipe[1]);
++
++  if (err == 0) {
++    do
++      r = read(signal_pipe[0], &exec_errorno, sizeof(exec_errorno));
++    while (r == -1 && errno == EINTR);
++
++    if (r == 0)
++      ; /* okay, EOF */
++    else if (r == sizeof(exec_errorno)) {
++      do
++        err = waitpid(*pid, &status, 0); /* okay, read errorno */
++      while (err == -1 && errno == EINTR);
++      assert(err == *pid);
++      err = exec_errorno;
++    } else if (r == -1 && errno == EPIPE) {
++      /* Something unknown happened to our child before spawn */
++      do
++        err = waitpid(*pid, &status, 0); /* okay, got EPIPE */
++      while (err == -1 && errno == EINTR);
++      assert(err == *pid);
++      err = UV_EPIPE;
++    } else
++      abort();
++  }
++
++  uv__close_nocheckstdio(signal_pipe[0]);
++
++  return err;
++}
++
+ int uv_spawn(uv_loop_t* loop,
+              uv_process_t* process,
+              const uv_process_options_t* options) {
+@@ -355,18 +920,13 @@ int uv_spawn(uv_loop_t* loop,
+   /* fork is marked __WATCHOS_PROHIBITED __TVOS_PROHIBITED. */
+   return UV_ENOSYS;
+ #else
+-  sigset_t signewset;
+-  sigset_t sigoldset;
+-  int signal_pipe[2] = { -1, -1 };
+   int pipes_storage[8][2];
+   int (*pipes)[2];
+   int stdio_count;
+-  ssize_t r;
+   pid_t pid;
+   int err;
+   int exec_errorno;
+   int i;
+-  int status;
+ 
+   assert(options->file != NULL);
+   assert(!(options->flags & ~(UV_PROCESS_DETACHED |
+@@ -379,6 +939,7 @@ int uv_spawn(uv_loop_t* loop,
+ 
+   uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS);
+   QUEUE_INIT(&process->queue);
++  process->status = 0;
+ 
+   stdio_count = options->stdio_count;
+   if (stdio_count < 3)
+@@ -403,106 +964,24 @@ int uv_spawn(uv_loop_t* loop,
+       goto error;
+   }
+ 
+-  /* This pipe is used by the parent to wait until
+-   * the child has called `execve()`. We need this
+-   * to avoid the following race condition:
+-   *
+-   *    if ((pid = fork()) > 0) {
+-   *      kill(pid, SIGTERM);
+-   *    }
+-   *    else if (pid == 0) {
+-   *      execve("/bin/cat", argp, envp);
+-   *    }
+-   *
+-   * The parent sends a signal immediately after forking.
+-   * Since the child may not have called `execve()` yet,
+-   * there is no telling what process receives the signal,
+-   * our fork or /bin/cat.
+-   *
+-   * To avoid ambiguity, we create a pipe with both ends
+-   * marked close-on-exec. Then, after the call to `fork()`,
+-   * the parent polls the read end until it EOFs or errors with EPIPE.
+-   */
+-  err = uv__make_pipe(signal_pipe, 0);
+-  if (err)
+-    goto error;
+-
+ #if !(defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__))
+   uv_signal_start(&loop->child_watcher, uv__chld, SIGCHLD);
+ #endif
+ 
+-  /* Acquire write lock to prevent opening new fds in worker threads */
+-  uv_rwlock_wrlock(&loop->cloexec_lock);
+-
+-  /* Start the child with most signals blocked, to avoid any issues before we
+-   * can reset them, but allow program failures to exit (and not hang). */
+-  sigfillset(&signewset);
+-  sigdelset(&signewset, SIGKILL);
+-  sigdelset(&signewset, SIGSTOP);
+-  sigdelset(&signewset, SIGTRAP);
+-  sigdelset(&signewset, SIGSEGV);
+-  sigdelset(&signewset, SIGBUS);
+-  sigdelset(&signewset, SIGILL);
+-  sigdelset(&signewset, SIGSYS);
+-  sigdelset(&signewset, SIGABRT);
+-  if (pthread_sigmask(SIG_BLOCK, &signewset, &sigoldset) != 0)
+-    abort();
+-
+-  pid = fork();
+-  if (pid == -1)
+-    err = UV__ERR(errno);
+-
+-  if (pid == 0)
+-    uv__process_child_init(options, stdio_count, pipes, signal_pipe[1]);
++  /* Spawn the child */
++  exec_errorno = uv__spawn_and_init_child(loop, options, stdio_count, pipes, &pid);
+ 
+-  if (pthread_sigmask(SIG_SETMASK, &sigoldset, NULL) != 0)
+-    abort();
+-
+-  /* Release lock in parent process */
+-  uv_rwlock_wrunlock(&loop->cloexec_lock);
+-
+-  uv__close(signal_pipe[1]);
+-
+-  if (pid == -1) {
+-    uv__close(signal_pipe[0]);
+-    goto error;
+-  }
+-
+-  process->status = 0;
+-  exec_errorno = 0;
+-  do
+-    r = read(signal_pipe[0], &exec_errorno, sizeof(exec_errorno));
+-  while (r == -1 && errno == EINTR);
+-
+-  if (r == 0)
+-    ; /* okay, EOF */
+-  else if (r == sizeof(exec_errorno)) {
+-    do
+-      err = waitpid(pid, &status, 0); /* okay, read errorno */
+-    while (err == -1 && errno == EINTR);
+-    assert(err == pid);
+-  } else if (r == -1 && errno == EPIPE) {
+-    do
+-      err = waitpid(pid, &status, 0); /* okay, got EPIPE */
+-    while (err == -1 && errno == EINTR);
+-    assert(err == pid);
+-  } else
+-    abort();
+-
+-  uv__close_nocheckstdio(signal_pipe[0]);
+-
+-  for (i = 0; i < options->stdio_count; i++) {
+-    err = uv__process_open_stream(options->stdio + i, pipes[i]);
+-    if (err == 0)
+-      continue;
+-
+-    while (i--)
+-      uv__process_close_stream(options->stdio + i);
+-
+-    goto error;
+-  }
++#if 0
++  /* This runs into a nodejs issue (it expects initialized streams, even if the
++   * exec failed).
++   * See https://github.com/libuv/libuv/pull/3107#issuecomment-782482608 */
++  if (exec_errorno != 0)
++      goto error;
++#endif
+ 
+-  /* Only activate this handle if exec() happened successfully */
++  /* Activate this handle if exec() happened successfully, even if we later
++   * fail to open a stdio handle. This ensures we can eventually reap the child
++   * with waitpid. */
+   if (exec_errorno == 0) {
+ #if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+     struct kevent event;
+@@ -515,12 +994,22 @@ int uv_spawn(uv_loop_t* loop,
+     }
+ #endif
+ 
++    process->pid = pid;
++    process->exit_cb = options->exit_cb;
+     QUEUE_INSERT_TAIL(&loop->process_handles, &process->queue);
+     uv__handle_start(process);
+   }
+ 
+-  process->pid = pid;
+-  process->exit_cb = options->exit_cb;
++  for (i = 0; i < options->stdio_count; i++) {
++    err = uv__process_open_stream(options->stdio + i, pipes[i]);
++    if (err == 0)
++      continue;
++
++    while (i--)
++      uv__process_close_stream(options->stdio + i);
++
++    goto error;
++  }
+ 
+   if (pipes != pipes_storage)
+     uv__free(pipes);

+ 173 - 0
patches/node/unix_protect_fork_in_uv_spawn_from_signals.patch

@@ -0,0 +1,173 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jameson Nash <[email protected]>
+Date: Thu, 29 Jul 2021 12:09:51 -0400
+Subject: unix: protect fork in uv_spawn from signals
+
+Years ago, we found that various kernels (linux, macOS) were known to
+fail if they try to deliver a signal during this syscall, so we prevent
+that from happening. They may have fixed those issues, but it is
+generally just a bad time for signals to arrive (glibc blocks them here,
+for example, including some more internal ones that it won't let us
+touch here).
+
+We try to be a bit conservative, and leave many signals unblocked which
+could happen during normal execution and should terminate the process if
+they do. There is a small race window after the child starts before we
+clear the old handlers, if the user was to send an fake signal from
+elsewhere, but that should be quite unlikely.
+
+PR-URL: https://github.com/libuv/libuv/pull/3251
+Reviewed-By: Ben Noordhuis <[email protected]>
+
+diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
+index cfcba341e0e380ecd595e4b59e39c08a7b374a48..c1f6bd4b0076f0835caf83c45a6a896e7ae5def9 100644
+--- a/deps/uv/src/unix/process.c
++++ b/deps/uv/src/unix/process.c
+@@ -26,6 +26,7 @@
+ #include <stdlib.h>
+ #include <assert.h>
+ #include <errno.h>
++#include <signal.h>
+ 
+ #include <sys/types.h>
+ #include <sys/wait.h>
+@@ -223,13 +224,32 @@ static void uv__process_child_init(const uv_process_options_t* options,
+                                    int stdio_count,
+                                    int (*pipes)[2],
+                                    int error_fd) {
+-  sigset_t set;
++  sigset_t signewset;
+   int close_fd;
+   int use_fd;
+-  int err;
+   int fd;
+   int n;
+ 
++  /* Reset signal disposition first. Use a hard-coded limit because NSIG is not
++   * fixed on Linux: it's either 32, 34 or 64, depending on whether RT signals
++   * are enabled. We are not allowed to touch RT signal handlers, glibc uses
++   * them internally.
++   */
++  for (n = 1; n < 32; n += 1) {
++    if (n == SIGKILL || n == SIGSTOP)
++      continue;  /* Can't be changed. */
++
++#if defined(__HAIKU__)
++    if (n == SIGKILLTHR)
++      continue;  /* Can't be changed. */
++#endif
++
++    if (SIG_ERR != signal(n, SIG_DFL))
++      continue;
++
++    uv__write_errno(error_fd);
++  }
++
+   if (options->flags & UV_PROCESS_DETACHED)
+     setsid();
+ 
+@@ -311,32 +331,10 @@ static void uv__process_child_init(const uv_process_options_t* options,
+     environ = options->env;
+   }
+ 
+-  /* Reset signal disposition.  Use a hard-coded limit because NSIG
+-   * is not fixed on Linux: it's either 32, 34 or 64, depending on
+-   * whether RT signals are enabled.  We are not allowed to touch
+-   * RT signal handlers, glibc uses them internally.
+-   */
+-  for (n = 1; n < 32; n += 1) {
+-    if (n == SIGKILL || n == SIGSTOP)
+-      continue;  /* Can't be changed. */
+-
+-#if defined(__HAIKU__)
+-    if (n == SIGKILLTHR)
+-      continue;  /* Can't be changed. */
+-#endif
+-
+-    if (SIG_ERR != signal(n, SIG_DFL))
+-      continue;
+-
+-    uv__write_errno(error_fd);
+-  }
+-
+-  /* Reset signal mask. */
+-  sigemptyset(&set);
+-  err = pthread_sigmask(SIG_SETMASK, &set, NULL);
+-
+-  if (err != 0)
+-    uv__write_errno(error_fd);
++  /* Reset signal mask just before exec. */
++  sigemptyset(&signewset);
++  if (sigprocmask(SIG_SETMASK, &signewset, NULL) != 0)
++    abort();
+ 
+ #ifdef __MVS__
+   execvpe(options->file, options->args, environ);
+@@ -345,6 +343,7 @@ static void uv__process_child_init(const uv_process_options_t* options,
+ #endif
+ 
+   uv__write_errno(error_fd);
++  abort();
+ }
+ #endif
+ 
+@@ -356,6 +355,8 @@ int uv_spawn(uv_loop_t* loop,
+   /* fork is marked __WATCHOS_PROHIBITED __TVOS_PROHIBITED. */
+   return UV_ENOSYS;
+ #else
++  sigset_t signewset;
++  sigset_t sigoldset;
+   int signal_pipe[2] = { -1, -1 };
+   int pipes_storage[8][2];
+   int (*pipes)[2];
+@@ -432,25 +433,41 @@ int uv_spawn(uv_loop_t* loop,
+ 
+   /* Acquire write lock to prevent opening new fds in worker threads */
+   uv_rwlock_wrlock(&loop->cloexec_lock);
+-  pid = fork();
+ 
+-  if (pid == -1) {
++  /* Start the child with most signals blocked, to avoid any issues before we
++   * can reset them, but allow program failures to exit (and not hang). */
++  sigfillset(&signewset);
++  sigdelset(&signewset, SIGKILL);
++  sigdelset(&signewset, SIGSTOP);
++  sigdelset(&signewset, SIGTRAP);
++  sigdelset(&signewset, SIGSEGV);
++  sigdelset(&signewset, SIGBUS);
++  sigdelset(&signewset, SIGILL);
++  sigdelset(&signewset, SIGSYS);
++  sigdelset(&signewset, SIGABRT);
++  if (pthread_sigmask(SIG_BLOCK, &signewset, &sigoldset) != 0)
++    abort();
++
++  pid = fork();
++  if (pid == -1)
+     err = UV__ERR(errno);
+-    uv_rwlock_wrunlock(&loop->cloexec_lock);
+-    uv__close(signal_pipe[0]);
+-    uv__close(signal_pipe[1]);
+-    goto error;
+-  }
+ 
+-  if (pid == 0) {
++  if (pid == 0)
+     uv__process_child_init(options, stdio_count, pipes, signal_pipe[1]);
++
++  if (pthread_sigmask(SIG_SETMASK, &sigoldset, NULL) != 0)
+     abort();
+-  }
+ 
+   /* Release lock in parent process */
+   uv_rwlock_wrunlock(&loop->cloexec_lock);
++
+   uv__close(signal_pipe[1]);
+ 
++  if (pid == -1) {
++    uv__close(signal_pipe[0]);
++    goto error;
++  }
++
+   process->status = 0;
+   exec_errorno = 0;
+   do

+ 91 - 0
patches/node/unix_remove_uv_cloexec_ioctl_3515.patch

@@ -0,0 +1,91 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jameson Nash <[email protected]>
+Date: Sun, 6 Mar 2022 15:01:33 -0500
+Subject: unix: remove uv__cloexec_ioctl() (#3515)
+
+Now that uv__cloexec_fcntl() is simplified
+(https://github.com/libuv/libuv/pull/3492), there is no benefit to
+maintaining duplicate code paths for the same thing.
+
+diff --git a/deps/uv/src/unix/core.c b/deps/uv/src/unix/core.c
+index 6cd519ad5b3e7af8f5c71b18a59b88458a233f15..8c30802ad15316a8ec20ecedfb5123174d74276b 100644
+--- a/deps/uv/src/unix/core.c
++++ b/deps/uv/src/unix/core.c
+@@ -597,20 +597,6 @@ int uv__nonblock_ioctl(int fd, int set) {
+ 
+   return 0;
+ }
+-
+-
+-int uv__cloexec_ioctl(int fd, int set) {
+-  int r;
+-
+-  do
+-    r = ioctl(fd, set ? FIOCLEX : FIONCLEX);
+-  while (r == -1 && errno == EINTR);
+-
+-  if (r)
+-    return UV__ERR(errno);
+-
+-  return 0;
+-}
+ #endif
+ 
+ 
+@@ -645,7 +631,7 @@ int uv__nonblock_fcntl(int fd, int set) {
+ }
+ 
+ 
+-int uv__cloexec_fcntl(int fd, int set) {
++int uv__cloexec(int fd, int set) {
+   int flags;
+   int r;
+ 
+diff --git a/deps/uv/src/unix/internal.h b/deps/uv/src/unix/internal.h
+index 2dcc8b32f5165dd75061a1b55cc1abd2ab93ccc9..543993989b8765247e140e9d950a25192e5e1ca5 100644
+--- a/deps/uv/src/unix/internal.h
++++ b/deps/uv/src/unix/internal.h
+@@ -175,11 +175,9 @@ struct uv__stream_queued_fds_s {
+     defined(__linux__) || \
+     defined(__OpenBSD__) || \
+     defined(__NetBSD__)
+-#define uv__cloexec uv__cloexec_ioctl
+ #define uv__nonblock uv__nonblock_ioctl
+ #define UV__NONBLOCK_IS_IOCTL 1
+ #else
+-#define uv__cloexec uv__cloexec_fcntl
+ #define uv__nonblock uv__nonblock_fcntl
+ #define UV__NONBLOCK_IS_IOCTL 0
+ #endif
+@@ -197,8 +195,7 @@ struct uv__stream_queued_fds_s {
+ #endif
+ 
+ /* core */
+-int uv__cloexec_ioctl(int fd, int set);
+-int uv__cloexec_fcntl(int fd, int set);
++int uv__cloexec(int fd, int set);
+ int uv__nonblock_ioctl(int fd, int set);
+ int uv__nonblock_fcntl(int fd, int set);
+ int uv__close(int fd); /* preserves errno */
+diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
+index 7705068730cb0536998bad7d304cb87df99b72e8..b6f9756c6a6710f5f10762b9299cc35047b98097 100644
+--- a/deps/uv/src/unix/process.c
++++ b/deps/uv/src/unix/process.c
+@@ -283,7 +283,7 @@ static void uv__process_child_init(const uv_process_options_t* options,
+     if (pipes[fd][1] == -1)
+       uv__write_errno(error_fd);
+ #ifndef F_DUPFD_CLOEXEC /* POSIX 2008 */
+-    n = uv__cloexec_fcntl(pipes[fd][1], 1);
++    n = uv__cloexec(pipes[fd][1], 1);
+     if (n) {
+       uv__write_int(error_fd, n);
+       _exit(127);
+@@ -312,7 +312,7 @@ static void uv__process_child_init(const uv_process_options_t* options,
+ 
+     if (fd == use_fd) {
+       if (close_fd == -1) {
+-        n = uv__cloexec_fcntl(use_fd, 0);
++        n = uv__cloexec(use_fd, 0);
+         if (n) {
+           uv__write_int(error_fd, n);
+           _exit(127);

+ 36 - 0
patches/node/unix_simplify_uv_cloexec_fcntl_3492.patch

@@ -0,0 +1,36 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Ben Noordhuis <[email protected]>
+Date: Sat, 5 Mar 2022 18:55:49 +0100
+Subject: unix: simplify uv__cloexec_fcntl() (#3492)
+
+FD_CLOEXEC is the only defined flag for fcntl(F_SETFD) so don't bother
+getting the status of that flag first with fcntl(F_GETFD), just set it.
+
+diff --git a/deps/uv/src/unix/core.c b/deps/uv/src/unix/core.c
+index a6425294086ff2f1435fdd6866380d3aaaf68200..6cd519ad5b3e7af8f5c71b18a59b88458a233f15 100644
+--- a/deps/uv/src/unix/core.c
++++ b/deps/uv/src/unix/core.c
+@@ -649,21 +649,9 @@ int uv__cloexec_fcntl(int fd, int set) {
+   int flags;
+   int r;
+ 
+-  do
+-    r = fcntl(fd, F_GETFD);
+-  while (r == -1 && errno == EINTR);
+-
+-  if (r == -1)
+-    return UV__ERR(errno);
+-
+-  /* Bail out now if already set/clear. */
+-  if (!!(r & FD_CLOEXEC) == !!set)
+-    return 0;
+-
++  flags = 0;
+   if (set)
+-    flags = r | FD_CLOEXEC;
+-  else
+-    flags = r & ~FD_CLOEXEC;
++    flags = FD_CLOEXEC;
+ 
+   do
+     r = fcntl(fd, F_SETFD, flags);