diff --git a/newlib/libc/posix/posix_spawn.c b/newlib/libc/posix/posix_spawn.c index 19c5cd0fe..005471fde 100644 --- a/newlib/libc/posix/posix_spawn.c +++ b/newlib/libc/posix/posix_spawn.c @@ -254,6 +254,69 @@ process_file_actions(const posix_spawn_file_actions_t fa) return (0); } +#ifdef __CYGWIN__ +/* Cygwin's vfork does not follow BSD vfork semantics. Rather it's equivalent + to fork. While that's POSIX compliant, the below FreeBSD implementation + relying on BSD vfork semantics doesn't work as expected on Cygwin. The + following Cygwin-specific code handles the synchronization FreeBSD gets + for free by using vfork. */ + +extern int __posix_spawn_sem_create (void **semp); +extern void __posix_spawn_sem_release (void *sem, int error); +extern int __posix_spawn_sem_wait_and_close (void *sem, void *proc); +extern int __posix_spawn_fork (void **proc); +extern int __posix_spawn_execvpe (const char *path, char * const *argv, + char *const *envp, void *sem, + int use_env_path); + + +static int +do_posix_spawn(pid_t *pid, const char *path, + const posix_spawn_file_actions_t *fa, + const posix_spawnattr_t *sa, + char * const argv[], char * const envp[], int use_env_path) +{ + int error; + void *sem, *proc; + pid_t p; + + error = __posix_spawn_sem_create(&sem); + if (error) + return error; + + p = __posix_spawn_fork(&proc); + switch (p) { + case -1: + return (errno); + case 0: + if (sa != NULL) { + error = process_spawnattr(*sa); + if (error) { + __posix_spawn_sem_release(sem, error); + _exit(127); + } + } + if (fa != NULL) { + error = process_file_actions(*fa); + if (error) { + __posix_spawn_sem_release(sem, error); + _exit(127); + } + } + __posix_spawn_execvpe(path, argv, + envp != NULL ? envp : *p_environ, + sem, use_env_path); + _exit(127); + default: + error = __posix_spawn_sem_wait_and_close(sem, proc); + if (error != 0) + waitpid(p, NULL, WNOHANG); + else if (pid != NULL) + *pid = p; + return (error); + } +} +#else static int do_posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *fa, @@ -292,6 +355,7 @@ do_posix_spawn(pid_t *pid, const char *path, return (error); } } +#endif int posix_spawn (pid_t *pid, diff --git a/winsup/cygwin/child_info.h b/winsup/cygwin/child_info.h index 2fa71ba69..e5aa28144 100644 --- a/winsup/cygwin/child_info.h +++ b/winsup/cygwin/child_info.h @@ -37,7 +37,7 @@ enum child_status #define EXEC_MAGIC_SIZE sizeof(child_info) /* Change this value if you get a message indicating that it is out-of-sync. */ -#define CURR_CHILD_INFO_MAGIC 0xf4531879U +#define CURR_CHILD_INFO_MAGIC 0xecc930b9U #define NPROCS 256 @@ -144,6 +144,7 @@ class child_info_spawn: public child_info { HANDLE hExeced; HANDLE ev; + HANDLE sem; pid_t cygpid; public: cygheap_exec_info *moreinfo; @@ -159,6 +160,11 @@ public: void *operator new (size_t, void *p) __attribute__ ((nothrow)) {return p;} void set (child_info_types ci, bool b) { new (this) child_info_spawn (ci, b);} void __reg1 handle_spawn (); + void set_sem (HANDLE _sem) + { + /* Don't leak semaphore handle into exec'ed process. */ + SetHandleInformation (sem = _sem, HANDLE_FLAG_INHERIT, 0); + } bool set_saw_ctrl_c () { if (!has_execed ()) @@ -188,8 +194,8 @@ public: bool get_parent_handle (); bool has_execed_cygwin () const { return iscygwin () && has_execed (); } operator HANDLE& () {return hExeced;} - int __reg3 worker (const char *, const char *const *, const char *const [], int, - int = -1, int = -1);; + int __reg3 worker (const char *, const char *const *, const char *const [], + int, int = -1, int = -1); }; extern child_info_spawn ch_spawn; diff --git a/winsup/cygwin/fork.cc b/winsup/cygwin/fork.cc index 691d08137..7c07b062e 100644 --- a/winsup/cygwin/fork.cc +++ b/winsup/cygwin/fork.cc @@ -31,7 +31,7 @@ details. */ /* FIXME: Once things stabilize, bump up to a few minutes. */ #define FORK_WAIT_TIMEOUT (300 * 1000) /* 300 seconds */ -static int dofork (bool *with_forkables); +static int dofork (void **proc, bool *with_forkables); class frok { frok (bool *forkables) @@ -47,7 +47,7 @@ class frok int __stdcall parent (volatile char * volatile here); int __stdcall child (volatile char * volatile here); bool error (const char *fmt, ...); - friend int dofork (bool *with_forkables); + friend int dofork (void **proc, bool *with_forkables); }; static void @@ -583,17 +583,36 @@ extern "C" int fork () { bool with_forkables = false; /* do not force hardlinks on first try */ - int res = dofork (&with_forkables); + int res = dofork (NULL, &with_forkables); if (res >= 0) return res; if (with_forkables) return res; /* no need for second try when already enabled */ with_forkables = true; /* enable hardlinks for second try */ - return dofork (&with_forkables); + return dofork (NULL, &with_forkables); +} + + +/* __posix_spawn_fork is called from newlib's posix_spawn implementation. + The original code in newlib has been taken from FreeBSD, and the core + code relies on specific, non-portable behaviour of vfork(2). Our + replacement implementation needs the forked child's HANDLE for + synchronization, so __posix_spawn_fork returns it in proc. */ +extern "C" int +__posix_spawn_fork (void **proc) +{ + bool with_forkables = false; /* do not force hardlinks on first try */ + int res = dofork (proc, &with_forkables); + if (res >= 0) + return res; + if (with_forkables) + return res; /* no need for second try when already enabled */ + with_forkables = true; /* enable hardlinks for second try */ + return dofork (proc, &with_forkables); } static int -dofork (bool *with_forkables) +dofork (void **proc, bool *with_forkables) { frok grouped (with_forkables); @@ -671,6 +690,11 @@ dofork (bool *with_forkables) set_errno (grouped.this_errno); } + else if (proc) + { + /* Return child process handle to posix_fork. */ + *proc = grouped.hchild; + } syscall_printf ("%R = fork()", res); return res; } diff --git a/winsup/cygwin/spawn.cc b/winsup/cygwin/spawn.cc index 3e8c8367a..840ec4a86 100644 --- a/winsup/cygwin/spawn.cc +++ b/winsup/cygwin/spawn.cc @@ -252,6 +252,8 @@ struct system_call_handle child_info_spawn NO_COPY ch_spawn; +extern "C" void __posix_spawn_sem_release (void *sem, int error); + int child_info_spawn::worker (const char *prog_arg, const char *const *argv, const char *const envp[], int mode, @@ -897,6 +899,8 @@ child_info_spawn::worker (const char *prog_arg, const char *const *argv, && WaitForSingleObject (pi.hProcess, 0) == WAIT_TIMEOUT) wait_for_myself (); } + if (sem) + __posix_spawn_sem_release (sem, 0); myself.exit (EXITCODE_NOSET); break; case _P_WAIT: @@ -1295,3 +1299,103 @@ err: __seterrno (); return -1; } + +/* The following __posix_spawn_* functions are called from newlib's posix_spawn + implementation. The original code in newlib has been taken from FreeBSD, + and the core code relies on specific, non-portable behaviour of vfork(2). + Our replacement implementation uses a semaphore to synchronize parent and + child process. Note: __posix_spawn_fork in fork.cc is part of the set. */ + +/* Create an inheritable semaphore. Set it to 0 (== non-signalled), so the + parent can wait on the semaphore immediately. */ +extern "C" int +__posix_spawn_sem_create (void **semp) +{ + HANDLE sem; + OBJECT_ATTRIBUTES attr; + NTSTATUS status; + + if (!semp) + return EINVAL; + InitializeObjectAttributes (&attr, NULL, OBJ_INHERIT, NULL, NULL); + status = NtCreateSemaphore (&sem, SEMAPHORE_ALL_ACCESS, &attr, 0, INT_MAX); + if (!NT_SUCCESS (status)) + return geterrno_from_nt_status (status); + *semp = sem; + return 0; +} + +/* Signal the semaphore. "error" should be 0 if all went fine and the + exec'd child process is up and running, a useful POSIX error code otherwise. + After releasing the semaphore, the value of the semaphore reflects + the error code + 1. Thus, after WFMO in__posix_spawn_sem_wait_and_close, + querying the value of the semaphore returns either 0 if all went well, + or a value > 0 equivalent to the POSIX error code. */ +extern "C" void +__posix_spawn_sem_release (void *sem, int error) +{ + ReleaseSemaphore (sem, error + 1, NULL); +} + +/* Helper to check the semaphore value. */ +static inline int +__posix_spawn_sem_query (void *sem) +{ + SEMAPHORE_BASIC_INFORMATION sbi; + + NtQuerySemaphore (sem, SemaphoreBasicInformation, &sbi, sizeof sbi, NULL); + return sbi.CurrentCount; +} + +/* Called from parent to wait for fork/exec completion. We're waiting for + the semaphore as well as the child's process handle, so even if the + child crashes without signalling the semaphore, we won't wait infinitely. */ +extern "C" int +__posix_spawn_sem_wait_and_close (void *sem, void *proc) +{ + int ret = 0; + HANDLE w4[2] = { sem, proc }; + + switch (WaitForMultipleObjects (2, w4, FALSE, INFINITE)) + { + case WAIT_OBJECT_0: + ret = __posix_spawn_sem_query (sem); + break; + case WAIT_OBJECT_0 + 1: + /* If we return here due to the child process dying, the semaphore is + very likely not signalled. Check this here and return a valid error + code. */ + ret = __posix_spawn_sem_query (sem); + if (ret == 0) + ret = ECHILD; + break; + default: + ret = geterrno_from_win_error (); + break; + } + + CloseHandle (sem); + return ret; +} + +/* Replacement for execve/execvpe, called from forked child in newlib's + posix_spawn. The relevant difference is the additional semaphore + so the worker method (which is not supposed to return on success) + can signal the semaphore after sync'ing with the exec'd child. */ +extern "C" int +__posix_spawn_execvpe (const char *path, char * const *argv, char *const *envp, + HANDLE sem, int use_env_path) +{ + path_conv buf; + + static char *const empty_env[] = { NULL }; + if (!envp) + envp = empty_env; + ch_spawn.set_sem (sem); + ch_spawn.worker (use_env_path ? (find_exec (path, buf, "PATH", FE_NNF) ?: "") + : path, + argv, envp, + _P_OVERLAY | (use_env_path ? _P_PATH_TYPE_EXEC : 0)); + __posix_spawn_sem_release (sem, errno); + return -1; +}