summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--newlib/libc/posix/posix_spawn.c64
-rw-r--r--winsup/cygwin/child_info.h12
-rw-r--r--winsup/cygwin/fork.cc34
-rw-r--r--winsup/cygwin/spawn.cc104
4 files changed, 206 insertions, 8 deletions
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;
+}