Skip to content

Switch to posix_spawn on Apple targets in SystemNative_ForkAndExecProcess#126063

Draft
Copilot wants to merge 2 commits intocopilot/add-standard-input-output-propertiesfrom
copilot/switch-to-posix-spawn-apple-targets
Draft

Switch to posix_spawn on Apple targets in SystemNative_ForkAndExecProcess#126063
Copilot wants to merge 2 commits intocopilot/add-standard-input-output-propertiesfrom
copilot/switch-to-posix-spawn-apple-targets

Conversation

Copy link
Contributor

Copilot AI commented Mar 24, 2026

Description

Uses posix_spawn instead of fork/exec in SystemNative_ForkAndExecProcess on macOS (excluding MacCatalyst and tvOS).

  • Guard: #if defined(__APPLE__) && !defined(TARGET_MACCATALYST) && !defined(TARGET_TVOS), placed as the first code block in the function
  • Credential fallback: When setCredentials is true, falls through to the existing fork/exec path since macOS posix_spawn does not support setuid/setgid
  • Signal handling: Sets POSIX_SPAWN_SETSIGDEF (reset all handlers to default) and POSIX_SPAWN_SETSIGMASK (empty mask, unblock all signals) for parity with the fork path
  • FD redirection: Uses posix_spawn_file_actions_adddup2 for stdin/stdout/stderr, skipping when fd is -1
  • Working directory: Calls posix_spawn_file_actions_addchdir_np directly (always available on supported macOS versions, no HAVE_ constant)
  • Scoped out: No POSIX_SPAWN_CLOEXEC_DEFAULT (separate PR), no create_suspended, no create_new_process_group

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates SystemNative_ForkAndExecProcess to use posix_spawn on Apple targets (intended for macOS per description) as an alternative to the existing fork/exec implementation when credentials don’t need to be set.

Changes:

  • Add Apple-only posix_spawn fast path (when setCredentials is false), including working-directory and stdio redirection setup.
  • Configure spawn attributes for signal defaults and signal mask behavior.
  • Add <spawn.h> include for Apple builds.

Comment on lines +259 to +262
// Reset all signal handlers to default
sigset_t all_signals;
sigfillset(&all_signals);
if ((result = posix_spawnattr_setsigdefault(&attr, &all_signals)) != 0)
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The posix_spawn signal-default setup resets all signals to SIG_DFL via sigfillset. This diverges from the existing fork path which preserves SIG_IGN handlers and skips signals like SIGKILL/SIGSTOP when adjusting handlers. To preserve behavior and avoid invalid entries in the sigdefault set, build the sigdefault set based on the current handlers (only signals with custom handlers), and exclude non-overridable signals.

Copilot uses AI. Check for mistakes.
Comment on lines +270 to +274
// Set the child's signal mask to empty (unblock all signals)
sigset_t no_signals;
sigemptyset(&no_signals);
if ((result = posix_spawnattr_setsigmask(&attr, &no_signals)) != 0)
{
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The posix_spawn path sets the child signal mask to an empty set (unblocks everything). The existing fork/vfork path restores the parent’s pre-fork mask (old_signal_set) in the child before exec. To keep parity, capture the current thread signal mask in the parent and pass that to posix_spawnattr_setsigmask instead of forcing an empty mask.

Suggested change
// Set the child's signal mask to empty (unblock all signals)
sigset_t no_signals;
sigemptyset(&no_signals);
if ((result = posix_spawnattr_setsigmask(&attr, &no_signals)) != 0)
{
// Set the child's signal mask to match the parent's current mask
sigset_t current_mask;
int sigmaskResult = pthread_sigmask(0, NULL, &current_mask);
if (sigmaskResult != 0)
{
int saved_errno = sigmaskResult;
posix_spawnattr_destroy(&attr);
errno = saved_errno;
return -1;
}
if ((result = posix_spawnattr_setsigmask(&attr, &current_mask)) != 0)
{

Copilot uses AI. Check for mistakes.
Comment on lines +231 to +246
// Make sure we can find and access the executable.
if (access(filename, X_OK) != 0)
{
return -1;
}

pid_t spawnedPid;
posix_spawn_file_actions_t file_actions;
posix_spawnattr_t attr;
int result;

if ((result = posix_spawnattr_init(&attr)) != 0)
{
errno = result;
return -1;
}
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On several error returns in the posix_spawn fast path (e.g., access failure, posix_spawnattr_init failure, or later setup failures), *childPid is left unchanged. The existing fork path sets all out args (including *childPid = -1) on any failure. For consistent caller behavior, ensure *childPid is set to -1 on every failure return in this block.

Copilot uses AI. Check for mistakes.
Comment on lines +290 to +292
if ((stdinFd != -1 && (result = posix_spawn_file_actions_adddup2(&file_actions, stdinFd, STDIN_FILENO)) != 0)
|| (stdoutFd != -1 && (result = posix_spawn_file_actions_adddup2(&file_actions, stdoutFd, STDOUT_FILENO)) != 0)
|| (stderrFd != -1 && (result = posix_spawn_file_actions_adddup2(&file_actions, stderrFd, STDERR_FILENO)) != 0))
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fork path skips dup2 when the provided fd already equals STDIN_FILENO/STDOUT_FILENO/STDERR_FILENO. Here, posix_spawn_file_actions_adddup2 is called even when stdinFd == STDIN_FILENO (etc.). For parity (and to avoid any platform-specific EINVAL behavior for no-op dup2 actions), add the same fd != STD*_FILENO checks before adding the spawn file actions.

Suggested change
if ((stdinFd != -1 && (result = posix_spawn_file_actions_adddup2(&file_actions, stdinFd, STDIN_FILENO)) != 0)
|| (stdoutFd != -1 && (result = posix_spawn_file_actions_adddup2(&file_actions, stdoutFd, STDOUT_FILENO)) != 0)
|| (stderrFd != -1 && (result = posix_spawn_file_actions_adddup2(&file_actions, stderrFd, STDERR_FILENO)) != 0))
if ((stdinFd != -1 && stdinFd != STDIN_FILENO && (result = posix_spawn_file_actions_adddup2(&file_actions, stdinFd, STDIN_FILENO)) != 0)
|| (stdoutFd != -1 && stdoutFd != STDOUT_FILENO && (result = posix_spawn_file_actions_adddup2(&file_actions, stdoutFd, STDOUT_FILENO)) != 0)
|| (stderrFd != -1 && stderrFd != STDERR_FILENO && (result = posix_spawn_file_actions_adddup2(&file_actions, stderrFd, STDERR_FILENO)) != 0))

Copilot uses AI. Check for mistakes.
Comment on lines +223 to +226
#if defined(__APPLE__) && !defined(TARGET_MACCATALYST) && !defined(TARGET_TVOS)
// Use posix_spawn on macOS when credentials don't need to be set,
// since macOS does not support setuid/setgid with posix_spawn.
if (!setCredentials)
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The __APPLE__ guard here also includes iOS/watchOS targets (it only excludes MacCatalyst and tvOS). That doesn’t match the PR description (“on macOS”) and could inadvertently enable this posix_spawn path on platforms where Process/exec support is intentionally limited. Consider using a macOS-specific define (e.g., TARGET_OSX/TARGET_OSX && !TARGET_MACCATALYST) or explicitly excluding TARGET_IOS/TARGET_WATCHOS as appropriate for this repo’s Apple target macros.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants