diff options
Diffstat (limited to 'winsup/cygwin/sigproc.cc')
-rw-r--r-- | winsup/cygwin/sigproc.cc | 1345 |
1 files changed, 1345 insertions, 0 deletions
diff --git a/winsup/cygwin/sigproc.cc b/winsup/cygwin/sigproc.cc new file mode 100644 index 0000000..97a0f3a --- /dev/null +++ b/winsup/cygwin/sigproc.cc @@ -0,0 +1,1345 @@ +/* sigproc.cc: inter/intra signal and sub process handler + + Copyright 1997, 1998, 1999, 2000 Cygnus Solutions. + + Written by Christopher Faylor <cgf@cygnus.com> + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include <stdlib.h> +#include <time.h> +#include <sys/wait.h> +#include <errno.h> +#include <stdlib.h> +#include "winsup.h" + +extern BOOL allow_ntsec; + +/* + * Convenience defines + */ +#define WSSC 60000 // Wait for signal completion +#define WPSP 40000 // Wait for proc_subproc mutex +#define WSPX 20000 // Wait for wait_sig to terminate +#define WWSP 20000 // Wait for wait_subproc to terminate + +#define WAIT_SIG_PRIORITY THREAD_PRIORITY_HIGHEST + +#define TOTSIGS (NSIG + __SIGOFFSET) + +#define sip_printf(fmt, args...) sigproc_printf (fmt , ## args) + +#define wake_wait_subproc() SetEvent (events[0]) + +#define no_signals_available() (!hwait_sig || !sig_loop_wait) + +/* + * Global variables + */ +const char *__sp_fn ; +int __sp_ln; + +char NO_COPY myself_nowait_dummy[1] = {'0'};// Flag to sig_send that signal goes to + // current process but no wait is required +char NO_COPY myself_nowait_nonmain_dummy[1] = {'1'};// Flag to sig_send that signal goes to + // current process but no wait is required + // if this is not the main thread. + +HANDLE NO_COPY signal_arrived; // Event signaled when a signal has + // resulted in a user-specified + // function call +/* + * Common variables + */ + + +/* How long to wait for message/signals. Normally this is infinite. + * On termination, however, these are set to zero as a flag to exit. + */ + +#define Static static NO_COPY + +Static DWORD proc_loop_wait = 500; // Wait for subprocesses to exit +Static DWORD sig_loop_wait = 500; // Wait for signals to arrive + +Static HANDLE sigcatch_nonmain = NULL; // The semaphore signaled when + // signals are available for + // processing from non-main thread +Static HANDLE sigcatch_main = NULL; // Signalled when main thread sends a + // signal +Static HANDLE sigcatch_nosync = NULL; // Signal wait_sig to scan sigtodo + // but not to bother with any + // synchronization +Static HANDLE sigcomplete_main = NULL; // Event signaled when a signal has + // finished processing for the main + // thread +Static HANDLE sigcomplete_nonmain = NULL;// Semaphore raised for non-main + // threads when a signal has finished + // processing +Static HANDLE hwait_sig = NULL; // Handle of wait_sig thread +Static HANDLE hwait_subproc = NULL; // Handle of sig_subproc thread + +Static HANDLE wait_sig_inited = NULL; // Control synchronization of + // message queue startup +Static muto *sync_proc_subproc = NULL; // Control access to + // subproc stuff + +/* Used by WaitForMultipleObjects. These are handles to child processes. + */ +Static HANDLE events[PSIZE + 1] = {0}; // All my children's handles++ +#define hchildren (events + 1) // Where the children handles begin +Static pinfo *pchildren[PSIZE] = {NULL};// All my children info +Static pinfo *zombies[PSIZE] = {NULL}; // All my deceased children info +Static int nchildren = 0; // Number of active children +Static int nzombies = 0; // Number of deceased children + +Static waitq waitq_head = {0}; // Start of queue for wait'ing threads +Static waitq waitq_main; // Storage for main thread + +DWORD NO_COPY maintid = 0; // ID of the main thread +Static DWORD sigtid = 0; // ID of the signal thread + +int NO_COPY pending_signals = 0; // TRUE if signals pending + +/* Functions + */ +static int __stdcall checkstate (waitq *); +static BOOL __inline get_proc_lock (DWORD, DWORD); +static HANDLE __stdcall getsem (pinfo *, const char *, int, int); +static void __stdcall remove_child (int); +static void __stdcall remove_zombie (int); +static DWORD WINAPI wait_sig (VOID *arg); +static int __stdcall stopped_or_terminated (waitq *, pinfo *); +static DWORD WINAPI wait_subproc (VOID *); + +/* Determine if the parent process is alive. + */ + +BOOL __stdcall +my_parent_is_alive () +{ + DWORD res; + if (!parent_alive) + { + debug_printf ("No parent_alive mutex"); + res = FALSE; + } + else + for (int i = 0; i < 2; i++) + switch (res = WaitForSingleObject (parent_alive, 0)) + { + case WAIT_OBJECT_0: + debug_printf ("parent dead."); + res = FALSE; + goto out; + case WAIT_TIMEOUT: + debug_printf ("parent still alive"); + res = TRUE; + goto out; + case WAIT_FAILED: + DWORD werr = GetLastError (); + if (werr == ERROR_INVALID_HANDLE && i == 0) + continue; + system_printf ("WFSO for parent_alive(%p) failed, error %d", + parent_alive, werr); + res = FALSE; + goto out; + } +out: + return res; +} + +__inline static void +wait_for_me () +{ + /* See if this is the first signal call after initialization. + * If so, wait for notification that all initialization has completed. + * Then set the handle to NULL to avoid checking this again. + */ + if (wait_sig_inited) + { + (void) WaitForSingleObject (wait_sig_inited, INFINITE); + (void) ForceCloseHandle (wait_sig_inited); + wait_sig_inited = NULL; + } +} + +static BOOL __stdcall +proc_can_be_signalled (pinfo *p) +{ + if (p == myself_nowait || p == myself_nowait_nonmain || p == myself) + { + wait_for_me (); + return 1; + } + + return ISSTATE (p, PID_INITIALIZING) || + (((p)->process_state & (PID_ACTIVE | PID_IN_USE)) == + (PID_ACTIVE | PID_IN_USE)); +} + +/* Test to determine if a process really exists and is processing + * signals. + */ +BOOL __stdcall +proc_exists (pinfo *p) +{ + HANDLE h; + + if (p == NULL) + return FALSE; + + if (p == myself || p == myself_nowait_nonmain || p == myself_nowait) + return TRUE; + + if (p->process_state == PID_NOT_IN_USE || !p->dwProcessId) + return FALSE; + + sip_printf ("checking for existence of pid %d, window pid %d", p->pid, + p->dwProcessId); + if (p->ppid == myself->pid && p->hProcess != NULL) + { + sip_printf ("it's mine, process_state %x", p->process_state); + return proc_can_be_signalled (p); + } + + /* Note: Process is alive if OpenProcess() call fails due to permissions */ + if (((h = OpenProcess (STANDARD_RIGHTS_REQUIRED, FALSE, p->dwProcessId)) + != NULL) || (GetLastError () == ERROR_ACCESS_DENIED)) + { + sip_printf ("it exists, %p", h); + if (h) + { + DWORD rc = WaitForSingleObject (h, 0); + CloseHandle (h); + if (rc == WAIT_OBJECT_0) + return 0; + } + return proc_can_be_signalled (p); + } + + sip_printf ("it doesn't exist"); + /* If the parent pid does not exist, clean this process out of the pinfo + * table. It must have died abnormally. + */ + if ((p->pid == p->ppid) || (p->ppid == 1) || !proc_exists (procinfo (p->ppid))) + { + p->hProcess = NULL; + p->process_state = PID_NOT_IN_USE; + } + return FALSE; +} + +/* Handle all subprocess requests + */ +#define vchild ((pinfo *) val) +int __stdcall +proc_subproc (DWORD what, DWORD val) +{ + int rc = 1; + int potential_match; + DWORD exitcode; + pinfo *child; + int send_sigchld = 0; + waitq *w; + +#define wval ((waitq *) val) + + sip_printf ("args: %x, %d", what, val); + + if (!get_proc_lock (what, val)) // Serialize access to this function + { + sip_printf ("I am not ready"); + goto out1; + } + + switch (what) + { + /* Add a new subprocess to the children arrays. + * (usually called from the main thread) + */ + case PROC_ADDCHILD: + if (nchildren >= PSIZE - 1) + system_printf ("nchildren too large %d", nchildren); + if (WaitForSingleObject (vchild->hProcess, 0) != WAIT_TIMEOUT) + { + system_printf ("invalid process handle %p. pid %d, win pid %d", + vchild->hProcess, vchild->pid, vchild->dwProcessId); + rc = 0; + break; + } + + pchildren[nchildren] = vchild; + hchildren[nchildren] = vchild->hProcess; + ProtectHandle (vchild->hProcess); + sip_printf ("added pid %d to wait list, slot %d, winpid %p, handle %p", + vchild->pid, nchildren, vchild->dwProcessId, + vchild->hProcess); + + nchildren++; + wake_wait_subproc (); + break; + + /* A child is in the stopped state. Scan wait() queue to see if anyone + * should be notified. (Called from wait_sig thread) + */ + case PROC_CHILDSTOPPED: + child = myself; // Just to avoid accidental NULL dereference + sip_printf ("Received stopped notification"); + goto scan_wait; + + /* A child process had terminated. + * Possibly this is just due to an exec(). Cygwin implements an exec() + * as a "handoff" from one windows process to another. If child->hProcess + * is different from what is recorded in hchildren, then this is an exec(). + * Otherwise this is a normal child termination event. + * (called from wait_subproc thread) + */ + case PROC_CHILDTERMINATED: + rc = 0; + child = pchildren[val]; + if (GetExitCodeProcess (hchildren[val], &exitcode) && + hchildren[val] != child->hProcess) + { + sip_printf ("pid %d[%d], reparented old hProcess %p, new %p", + child->pid, val, hchildren[val], child->hProcess); + ForceCloseHandle1 (hchildren[val], childhProc); + hchildren[val] = child->hProcess; /* Filled out by child */ + ProtectHandle1 (child->hProcess, childhProc); + wake_wait_subproc (); + break; // This was an exec() + } + + sip_printf ("pid %d[%d] terminated, handle %p, nchildren %d, nzombies %d", + child->pid, val, hchildren[val], nchildren, nzombies); + remove_child (val); // Remove from children arrays + zombies[nzombies++] = child; // Add to zombie array + wake_wait_subproc (); // Notify wait_subproc thread that + // nchildren has changed. + child->process_state = PID_ZOMBIE;// Walking dead + if (!proc_loop_wait) // Don't bother if wait_subproc is + break; // exiting + + send_sigchld = 1; + + scan_wait: + /* Scan the linked list of wait()ing threads. If a wait's parameters + * match this pid, then activate it. + */ + for (w = &waitq_head; w->next != NULL; w = w->next) + { + if ((potential_match = checkstate (w)) > 0) + sip_printf ("released waiting thread"); + else if (potential_match < 0) + sip_printf ("only found non-terminated children"); + else if (potential_match == 0) // nothing matched + { + sip_printf ("waiting thread found no children"); + HANDLE oldw = w->next->ev; + w->next->ev = NULL; + if (!SetEvent (oldw)) + system_printf ("couldn't wake up wait event %p, %E", oldw); + w->next = w->next->next; + } + if (w->next == NULL) + break; + } + + sip_printf ("finished processing terminated/stopped child"); + if (!send_sigchld) + break; // No need to send a SIGCHLD + + /* Send a SIGCHLD to myself. */ + sync_proc_subproc->release (); // Avoid a potential deadlock + rc = sig_send (NULL, SIGCHLD); // Send a SIGCHLD + goto out1; // Don't try to unlock. We don't have a lock. + + + /* Clear all waiting threads. Called from exceptions.cc prior to + * the main thread's dispatch to a signal handler function. + * (called from wait_sig thread) + */ + case PROC_CLEARWAIT: + /* Clear all "wait"ing threads. */ + sip_printf ("clear waiting threads"); + for (w = &waitq_head; w->next != NULL; w = w->next) + { + sip_printf ("clearing waiting thread, pid %d", w->next->pid); + w->next->status = -1; /* flag that a signal was received */ + if (!SetEvent (w->next->ev)) + system_printf ("Couldn't wake up wait event, %E"); + } + waitq_head.next = NULL; + sip_printf ("finished clearing"); + break; + + /* Handle a wait4() operation. Allocates an event for the calling + * thread which is signaled when the appropriate pid exits or stops. + * (usually called from the main thread) + */ + case PROC_WAIT: + wval->ev = NULL; // Don't know event flag yet + + if (wval->pid <= 0) + child = NULL; // Not looking for a specific pid + else if ((child = procinfo (wval->pid)) == NULL) + goto out; // invalid pid. flag no such child + + wval->status = 0; // Don't know status yet + + /* Put waitq structure at the end of a linked list. */ + for (w = &waitq_head; w->next != NULL; w = w->next) + if (w->next == wval && (w->next = w->next->next) == NULL) + break; + + wval->next = NULL; /* This will be last in the list */ + sip_printf ("wval->pid %d, wval->options %d", wval->pid, wval->options); + + /* If the first time for this thread, create a new event, otherwise + * reset the event. + */ + if ((wval->ev = wval->thread_ev) == NULL) + { + wval->ev = wval->thread_ev = CreateEvent (&sec_none_nih, TRUE, + FALSE, NULL); + ProtectHandle (wval->ev); + } + ResetEvent (wval->ev); + + /* Scan list of children to see if any have died. + * If so, the event flag is set so that the wait* () + * process will return immediately. + * + * If no children were found and the wait option was WNOHANG, + * then set the pid to 0 and remove the waitq value from + * consideration. + */ + w->next = wval; /* set at end of wait queue */ + if ((potential_match = checkstate (w)) <= 0) + { + if (!potential_match) + { + w->next = NULL; // don't want to keep looking + wval->ev = NULL; // flag that there are no children + sip_printf ("no appropriate children, %p, %p", + wval->thread_ev, wval->ev); + } + else if (wval->options & WNOHANG) + { + w->next = NULL; // don't want to keep looking + wval->pid = 0; // didn't find a pid + if (!SetEvent (wval->ev)) // wake up wait4 () immediately + system_printf ("Couldn't wake up wait event, %E"); + sip_printf ("WNOHANG and no terminated children, %p, %p", + wval->thread_ev, wval->ev); + } + } + if (w->next != NULL) + sip_printf ("wait activated %p, %p", wval->thread_ev, wval->ev); + else if (wval->ev != NULL) + sip_printf ("wait activated %p. Reaped zombie.", wval->ev); + else + sip_printf ("wait not activated %p, %p", wval->thread_ev, wval->ev); + break; + } + +out: + sync_proc_subproc->release (); // Release the lock +out1: + sip_printf ("returning %d", rc); + return rc; +} + +/* Terminate the wait_subproc thread. + * Called on process exit. + * Also called by spawn_guts to disassociate any subprocesses from this + * process. Subprocesses will then know to clean up after themselves and + * will not become zombies. + */ +void __stdcall +proc_terminate (void) +{ + sip_printf ("nchildren %d, nzombies %d", nchildren, nzombies); + /* Signal processing is assumed to be blocked in this routine. */ + if (hwait_subproc) + { + int rc; + proc_loop_wait = 0; // Tell wait_subproc thread to exit + wake_wait_subproc (); // Wake wait_subproc loop + + /* Wait for wait_subproc thread to exit (but not *too* long) */ + if ((rc = WaitForSingleObject (hwait_subproc, WWSP)) != WAIT_OBJECT_0) + if (rc == WAIT_TIMEOUT) + system_printf ("WFSO(hwait_subproc) timed out"); + else + system_printf ("WFSO(hwait_subproc), rc %d, %E", rc); + + HANDLE h = hwait_subproc; + hwait_subproc = NULL; + ForceCloseHandle1 (h, hwait_subproc); + + sync_proc_subproc->acquire(WPSP); + (void) proc_subproc (PROC_CLEARWAIT, 0); + + lock_pinfo_for_update (INFINITE); + /* Clean out zombie processes from the pid list. */ + int i; + for (i = 0; i < nzombies; i++) + { + pinfo *child; + if ((child = zombies[i])->hProcess) + { + ForceCloseHandle1 (child->hProcess, childhProc); + child->hProcess = NULL; + } + child->process_state = PID_NOT_IN_USE; + } + + /* Disassociate my subprocesses */ + for (i = 0; i < nchildren; i++) + { + pinfo *child; + if ((child = pchildren[i])->process_state == PID_NOT_IN_USE) + continue; // Should never happen + if (!child->hProcess) + sip_printf ("%d(%d) hProcess cleared already?", child->pid, + child->dwProcessId); + else + { + ForceCloseHandle1 (child->hProcess, childhProc); + child->hProcess = NULL; + if (!proc_exists (child)) + { + sip_printf ("%d(%d) doesn't exist", child->pid, + child->dwProcessId); + child->process_state = PID_NOT_IN_USE; /* a reaped child */ + } + else + { + sip_printf ("%d(%d) closing active child handle", child->pid, + child->dwProcessId); + child->ppid = 1; + if (child->pgid == myself->pid) + child->process_state |= PID_ORPHANED; + } + } + } + unlock_pinfo (); + nchildren = nzombies = 0; + + /* Attempt to close and release sync_proc_subproc in a + * non-raceable manner. + */ + muto *m = sync_proc_subproc; + sync_proc_subproc = NULL; + delete m; + } + sip_printf ("leaving"); +} + +/* Clear pending signal from the sigtodo array + */ +void __stdcall +sig_clear (int sig) +{ + (void) InterlockedExchange (myself->getsigtodo(sig), 0L); + return; +} + +/* Force the wait_sig thread to wake up and scan the sigtodo array. + */ +extern "C" int __stdcall +sig_dispatch_pending (int force) +{ + if (!hwait_sig) + return 0; + + int was_pending = pending_signals; +#ifdef DEBUGGING + sip_printf ("pending_signals %d", was_pending); +#endif + if (!was_pending && !force) +#ifdef DEBUGGING + sip_printf ("no need to wake anything up"); +#else + ; +#endif + else + { + wait_for_me (); + if (ReleaseSemaphore (sigcatch_nosync, 1, NULL)) +#ifdef DEBUGGING + sip_printf ("woke up wait_sig"); +#else + ; +#endif + else if (no_signals_available ()) + /*sip_printf ("I'm going away now")*/; + else + system_printf ("%E releasing sigcatch_nosync(%p)", sigcatch_nosync); + } + return was_pending; +} + +/* Message initialization. Called from dll_crt0_1 + * + * This routine starts the signal handling thread. The wait_sig_inited + * event is used to signal that the thread is ready to handle signals. + * We don't wait for this during initialization but instead detect it + * in sig_send to gain a little concurrency. + */ +void __stdcall +sigproc_init () +{ + wait_sig_inited = CreateEvent (&sec_none_nih, FALSE, FALSE, NULL); + ProtectHandle (wait_sig_inited); + + /* local event signaled when main thread has been dispatched + to a signal handler function. */ + signal_arrived = CreateEvent(&sec_none_nih, TRUE, FALSE, NULL); + + maintid = GetCurrentThreadId ();// For use in determining if signals + // should be blocked. + + if (!(hwait_sig = makethread (wait_sig, NULL, 0, "sig"))) + { + system_printf ("cannot create wait_sig thread, %E"); + api_fatal ("terminating"); + } + + ProtectHandle (hwait_sig); + + /* sync_proc_subproc is used by proc_subproc. It serialises + * access to the children and zombie arrays. + */ + sync_proc_subproc = new_muto (FALSE, NULL); + + /* Initialize waitq structure for main thread. A waitq structure is + * allocated for each thread that executes a wait to allow multiple threads + * to perform waits. Pre-allocate a waitq structure for the main thread. + */ + waitq *w; + if ((w = (waitq *)waitq_storage.get ()) == NULL) + { + w = &waitq_main; + waitq_storage.set (w); + } + memset (w, 0, sizeof *w); // Just to be safe + + sip_printf ("process/signal handling enabled(%x)", myself->process_state); + return; +} + +/* Called on process termination to terminate signal and process threads. + */ +void __stdcall +sigproc_terminate (void) +{ + HANDLE h = hwait_sig; + hwait_sig = NULL; + + if (GetCurrentThreadId () == sigtid) + { + ForceCloseHandle (sigcomplete_main); + for (int i = 0; i < 20; i++) + (void) ReleaseSemaphore (sigcomplete_nonmain, 1, NULL); + ForceCloseHandle (sigcomplete_nonmain); + ForceCloseHandle (sigcatch_main); + ForceCloseHandle (sigcatch_nonmain); + ForceCloseHandle (sigcatch_nosync); + } + proc_terminate (); // Terminate process handling thread + + if (!sig_loop_wait) + sip_printf ("sigproc_terminate: sigproc handling not active"); + else + { + sigproc_printf ("entering"); + sig_loop_wait = 0; // Tell wait_sig to exit when it is + // finished with anything it is doing + sig_dispatch_pending (TRUE); // wake up and die + + /* If !hwait_sig, then the process probably hasn't even finished + * its initialization phase. + */ + if (hwait_sig) + { + if (GetCurrentThreadId () != sigtid) + WaitForSingleObject (h, 10000); + ForceCloseHandle1 (h, hwait_sig); + + /* Exiting thread. Cleanup. Don't set to inactive if a child has been + execed with the same pid. */ + if (!myself->dwProcessId || myself->dwProcessId == GetCurrentProcessId ()) + myself->process_state &= ~PID_ACTIVE; + else + sip_printf ("Did not clear PID_ACTIVE since %d != %d", + myself->dwProcessId, GetCurrentProcessId ()); + + /* In case of a sigsuspend */ + SetEvent (signal_arrived); + + if (GetCurrentThreadId () != sigtid) + { + ForceCloseHandle (sigcomplete_main); + ForceCloseHandle (sigcomplete_nonmain); + ForceCloseHandle (sigcatch_main); + ForceCloseHandle (sigcatch_nonmain); + ForceCloseHandle (sigcatch_nosync); + } + } + sip_printf ("done"); + } + + /* Set this so that subsequent tests will succeed. */ + if (!myself->dwProcessId) + myself->dwProcessId = GetCurrentProcessId (); + + return; +} + +/* Send a signal to another process by raising its signal semaphore. + * If pinfo *p == NULL, send to the current process. + * If sending to this process, wait for notification that a signal has + * completed before returning. + */ +int __stdcall +sig_send (pinfo *p, int sig) +{ + int rc = 1; + DWORD tid = GetCurrentThreadId (); + BOOL its_me; + HANDLE thiscatch = NULL; + HANDLE thiscomplete = NULL; + BOOL wait_for_completion; + + if (p == myself_nowait_nonmain) + p = (tid == maintid) ? myself : myself_nowait; + if (!(its_me = (p == NULL || p == myself || p == myself_nowait))) + wait_for_completion = FALSE; + else + { + if (no_signals_available ()) + goto out; // Either exiting or not yet initializing + wait_for_me (); + wait_for_completion = p != myself_nowait; + p = myself; + } + + /* It is possible that the process is not yet ready to receive messages + * or that it has exited. Detect this. + */ + if (!proc_can_be_signalled (p)) /* Is the process accepting messages? */ + { + sip_printf ("invalid pid %d(%x), signal %d", + p->pid, p->process_state, sig); + set_errno (ESRCH); + goto out; + } + + sip_printf ("pid %d, signal %d, its_me %d", p->pid, sig, its_me); + + if (its_me) + { + if (!wait_for_completion) + thiscatch = sigcatch_nosync; + else if (tid != maintid) + { + thiscatch = sigcatch_nonmain; + thiscomplete = sigcomplete_nonmain; + } + else + { + thiscatch = sigcatch_main; + thiscomplete = sigcomplete_main; + ResetEvent (thiscomplete); + } + } + else if (!(thiscatch = getsem (p, "sigcatch", 0, 0))) + goto out; // Couldn't get the semaphore. getsem issued + // an error, if appropriate. + +#if WHEN_MULTI_THREAD_SIGNALS_WORK + signal_dispatch *sd; + sd = signal_dispatch_storage.get (); + if (sd == NULL) + sd = signal_dispatch_storage.create (); +#endif + /* Increment the sigtodo array to signify which signal to assert. + */ + (void) InterlockedIncrement (p->getsigtodo(sig)); + + /* Notify the process that a signal has arrived. + */ + SetLastError (0); + if (!ReleaseSemaphore (thiscatch, 1, NULL) && (int) GetLastError () > 0) + { + /* Couldn't signal the semaphore. This probably means that the + * process is exiting. + */ + if (!its_me) + ForceCloseHandle (thiscatch); + else + { + if (no_signals_available ()) + sip_printf ("I'm going away now"); + else if ((int) GetLastError () == -1) + rc = WaitForSingleObject (thiscomplete, 500); + else + system_printf ("error sending signal %d to pid %d, semaphore %p, %E", + sig, p->pid, thiscatch); + } + goto out; + } + + /* No need to wait for signal completion unless this was a signal to + * this process. + * + * If it was a signal to this process, wait for a dispatched signal. + * Otherwise just wait for the wait_sig to signal that it has finished + * processing the signal. + */ + if (!wait_for_completion) + { + rc = WAIT_OBJECT_0; + sip_printf ("Not waiting for sigcomplete. its_me %d sig %d", its_me, sig); + if (!its_me) + ForceCloseHandle (thiscatch); + } + else + { + sip_printf ("Waiting for thiscomplete %p", thiscomplete); + + SetLastError (0); + rc = WaitForSingleObject (thiscomplete, WSSC); + /* Check for strangeness due to this thread being redirected by the + signal handler. Sometimes a WAIT_TIMEOUT will occur when the + thread hasn't really timed out. So, check again. + FIXME: This isn't foolproof. */ + if (rc != WAIT_OBJECT_0 && + WaitForSingleObject (thiscomplete, 0) == WAIT_OBJECT_0) + rc = WAIT_OBJECT_0; + } + + if (rc == WAIT_OBJECT_0) + rc = 0; // Successful exit + else + { + /* It's an error unless sig_loop_wait == 0 (the process is exiting). */ + if (!no_signals_available ()) + system_printf ("wait for sig_complete event failed, sig %d, rc %d, %E", + sig, rc); + set_errno (ENOSYS); + rc = -1; + } + +out: + sip_printf ("returning %d from sending signal %d", rc, sig); + return rc; +} + +/* Set pending signal from the sigtodo array + */ +void __stdcall +sig_set_pending (int sig) +{ + (void) InterlockedIncrement (myself->getsigtodo(sig)); + return; +} + +/* Initialize the wait_subproc thread. + * Called from fork() or spawn() to initialize the handling of subprocesses. + */ +void __stdcall +subproc_init (void) +{ + if (hwait_subproc) + return; + + /* A "wakeup" handle which can be toggled to make wait_subproc reexamine + * the hchildren array. + */ + events[0] = CreateEvent (&sec_none_nih, FALSE, FALSE, NULL); + if (!(hwait_subproc = makethread (wait_subproc, NULL, 0, "+proc"))) + system_printf ("cannot create wait_subproc thread, %E"); + ProtectHandle (events[0]); + ProtectHandle (hwait_subproc); + sip_printf ("started wait_subproc thread %p", hwait_subproc); +} + +/* Initialize some of the memory block passed to child processes + by fork/spawn/exec. */ + +void __stdcall +init_child_info (DWORD chtype, child_info *ch, int pid, HANDLE subproc_ready) +{ + subproc_init (); + memset (ch, 0, sizeof *ch); + ch->cb = sizeof *ch; + ch->type = chtype; + ch->cygpid = pid; + ch->shared_h = cygwin_shared_h; + ch->console_h = console_shared_h; + ch->subproc_ready = subproc_ready; + if (chtype != PROC_EXEC || !parent_alive) + ch->parent_alive = hwait_subproc; + else if (parent_alive) + DuplicateHandle (hMainProc, parent_alive, hMainProc, &ch->parent_alive, + 0, 1, DUPLICATE_SAME_ACCESS); +} + +/* Check the state of all of our children to see if any are stopped or + * terminated. + */ +static int __stdcall +checkstate (waitq *w) +{ + int i, x, potential_match = 0; + pinfo *child; + + sip_printf ("nchildren %d, nzombies %d", nchildren, nzombies); + + /* Check already dead processes first to see if they match the criteria + * given in w->next. + */ + for (i = 0; i < nzombies; i++) + if ((x = stopped_or_terminated (w, child = zombies[i])) < 0) + potential_match = -1; + else if (x > 0) + { + remove_zombie (i); + potential_match = 1; + goto out; + } + + sip_printf ("checking alive children"); + + /* No dead terminated children matched. Check for stopped children. */ + for (i = 0; i < nchildren; i++) + if ((x = stopped_or_terminated (w, pchildren[i])) < 0) + potential_match = -1; + else if (x > 0) + { + potential_match = 1; + break; + } + +out: + sip_printf ("returning %d", potential_match); + return potential_match; +} + +/* Get or create a process specific semaphore used in message passing. + */ +static HANDLE __stdcall +getsem (pinfo *p, const char *str, int init, int max) +{ + HANDLE h; + + if (p != NULL) + { + if (!proc_can_be_signalled (p)) + { + set_errno (ESRCH); + return NULL; + } + int wait = 10000; + sip_printf ("pid %d, ppid %d, wait %d, initializing %x", p->pid, p->ppid, wait, + ISSTATE (p, PID_INITIALIZING)); + for (int i = 0; ISSTATE (p, PID_INITIALIZING) && i < wait; i++) + Sleep (1); + } + + SetLastError (0); + if (p == NULL) + { + char sa_buf[1024]; + + DWORD winpid = GetCurrentProcessId (); + h = CreateSemaphore (allow_ntsec ? sec_user (sa_buf) : &sec_none_nih, + init, max, str = shared_name (str, winpid)); + p = myself; + } + else + { + h = OpenSemaphore (SEMAPHORE_ALL_ACCESS, FALSE, + str = shared_name (str, p->dwProcessId)); + + if (h == NULL) + { + if (GetLastError () == ERROR_FILE_NOT_FOUND && !proc_exists (p)) + set_errno (ESRCH); + else + set_errno (EPERM); + return NULL; + } + } + + if (!h) + { + system_printf ("can't %s %s, %E", p ? "open" : "create", str); + set_errno (ESRCH); + } + return h; +} + +/* Get the sync_proc_subproc muto to control access to + * children, zombie arrays. + * Attempt to handle case where process is exiting as we try to grab + * the mutex. + */ +static BOOL __inline +get_proc_lock (DWORD what, DWORD val) +{ + Static int lastwhat = -1; + if (!sync_proc_subproc) + return FALSE; + if (sync_proc_subproc->acquire (WPSP)) + { + lastwhat = what; + return TRUE; + } + if (!sync_proc_subproc) + return FALSE; + system_printf ("Couldn't aquire sync_proc_subproc for(%d,%d), %E, last %d", + what, val, lastwhat); + return TRUE; +} + +/* Remove a child from pchildren/hchildren by swapping it with the + * last child in the list. + */ +static void __stdcall +remove_child (int ci) +{ + sip_printf ("removing [%d], pid %d, handle %p, nchildren %d", + ci, pchildren[ci]->pid, hchildren[ci], nchildren); + if (ci < --nchildren) + { + pchildren[ci] = pchildren[nchildren]; + hchildren[ci] = hchildren[nchildren]; + } + + return; +} + +/* Remove a zombie from zombies by swapping it with the last child in the list. + */ +static void __stdcall +remove_zombie (int ci) +{ + sip_printf ("removing %d, pid %d, nzombies %d", ci, zombies[ci]->pid, + nzombies); + if (ci < --nzombies) + zombies[ci] = zombies[nzombies]; + + return; +} + +/* Check status of child process vs. waitq member. + * + * parent_w is the pointer to the parent of the waitq member in question. + * child is the subprocess being considered. + * + * Returns + * 1 if stopped or terminated child matches parent_w->next criteria + * -1 if a non-stopped/terminated child matches parent_w->next criteria + * 0 if child does not match parent_w->next criteria + */ +static int __stdcall +stopped_or_terminated (waitq *parent_w, pinfo *child) +{ + int potential_match; + waitq *w = parent_w->next; + + sip_printf ("considering pid %d", child->pid); + if (w->pid == -1) + potential_match = 1; + else if (w->pid == 0) + potential_match = child->pgid == myself->pgid; + else if (w->pid < 0) + potential_match = child->pgid == -w->pid; + else + potential_match = (w->pid == child->pid); + + if (!potential_match) + return 0; + + BOOL terminated; + + if ((terminated = child->process_state == PID_ZOMBIE) || + (w->options & WUNTRACED) && child->stopsig) + { + parent_w->next = w->next; /* successful wait. remove from wait queue */ + w->pid = child->pid; + + if (!terminated) + { + sip_printf ("stopped child"); + w->status = (child->stopsig << 8) | 0x7f; + child->stopsig = 0; + } + else + { + DWORD status; + if (!GetExitCodeProcess (child->hProcess, &status)) + status = 0xffff; + if (status & EXIT_SIGNAL) + w->status = (status >> 8) & 0xff; /* exited due to signal */ + else + w->status = (status & 0xff) << 8; /* exited via "exit ()" */ + + add_rusage (&myself->rusage_children, &child->rusage_children); + add_rusage (&myself->rusage_children, &child->rusage_self); + + if (w->rusage) + { + add_rusage ((struct rusage *) w->rusage, &child->rusage_children); + add_rusage ((struct rusage *) w->rusage, &child->rusage_self); + } + ForceCloseHandle1 (child->hProcess, childhProc); + child->hProcess = NULL; + child->process_state = PID_NOT_IN_USE; /* a reaped child */ + } + + if (!SetEvent (w->ev)) /* wake up wait4 () immediately */ + system_printf ("couldn't wake up wait event %p, %E", w->ev); + return 1; + } + + return -potential_match; +} + +/* Process signals by waiting for a semaphore to become signaled. + * Then scan an in-memory array representing queued signals. + * Executes in a separate thread. + * + * Signals sent from this process are sent a completion signal so + * that returns from kill/raise do not occur until the signal has + * has been handled, as per POSIX. + */ +static DWORD WINAPI +wait_sig (VOID *arg) +{ + /* Initialization */ + (void) SetThreadPriority (hwait_sig, WAIT_SIG_PRIORITY); + + /* sigcatch_nosync - semaphore incremented by sig_dispatch_pending and + * by foreign processes to force an examination of + * the sigtodo array. + * sigcatch_main - ditto for local main thread. + * sigcatch_nonmain - ditto for local non-main threads. + * + * sigcomplete_main - event used to signal main thread on signal + * completion + * sigcomplete_nonmain - semaphore signaled for non-main thread on signal + * completion + */ + sigcatch_nosync = getsem (NULL, "sigcatch", 0, MAXLONG); + sigcatch_nonmain = CreateSemaphore (&sec_none_nih, 0, MAXLONG, NULL); + sigcatch_main = CreateSemaphore (&sec_none_nih, 0, MAXLONG, NULL); + sigcomplete_nonmain = CreateSemaphore (&sec_none_nih, 0, MAXLONG, NULL); + sigcomplete_main = CreateEvent (&sec_none_nih, TRUE, FALSE, NULL); + sigproc_printf ("sigcatch_nonmain %p", sigcatch_nonmain); + + /* Setting dwProcessId flags that this process is now capable of receiving + * signals. Prior to this, dwProcessId was set to the windows pid of + * of the original windows process which spawned us unless this was a + * "toplevel" process. + */ + myself->dwProcessId = GetCurrentProcessId (); + myself->process_state |= PID_ACTIVE; + myself->process_state &= ~PID_INITIALIZING; + + ProtectHandle (sigcatch_nosync); + ProtectHandle (sigcatch_nonmain); + ProtectHandle (sigcatch_main); + ProtectHandle (sigcomplete_nonmain); + ProtectHandle (sigcomplete_main); + + /* If we've been execed, then there is still a stub left in the previous + * windows process waiting to see if it's started a cygwin process or not. + * Signalling subproc_ready indicates that we are a cygwin process. + */ + if (child_proc_info && child_proc_info->type == PROC_EXEC) + { + debug_printf ("subproc_ready %p", child_proc_info->subproc_ready); + if (!SetEvent (child_proc_info->subproc_ready)) + system_printf ("SetEvent (subproc_ready) failed, %E"); + ForceCloseHandle (child_proc_info->subproc_ready); + } + + SetEvent (wait_sig_inited); + sigtid = GetCurrentThreadId (); + + /* If we got something like a SIGINT while we were initializing, the + signal thread should be waiting for this event. This signals the + thread that it's ok to send the signal since the wait_sig thread + is now active. */ + extern HANDLE console_handler_thread_waiter; + SetEvent (console_handler_thread_waiter); + + HANDLE catchem[] = {sigcatch_main, sigcatch_nonmain, sigcatch_nosync}; + sigproc_printf ("Ready. dwProcessid %d", myself->dwProcessId); + for (;;) + { + DWORD rc = WaitForMultipleObjects (3, catchem, FALSE, sig_loop_wait); + + /* sigproc_terminate sets sig_loop_wait to zero to indicate that + * this thread should terminate. + */ + if (rc == WAIT_TIMEOUT) + if (!sig_loop_wait) + break; // Exiting + else + continue; + + if (rc == WAIT_FAILED) + { + if (sig_loop_wait != 0) + system_printf ("WFMO failed, %E"); + break; + } + + rc -= WAIT_OBJECT_0; + int dispatched = FALSE; + sip_printf ("awake"); + /* A sigcatch semaphore has been signaled. Scan the sigtodo + * array looking for any unprocessed signals. + */ + pending_signals = 0; + for (int sig = -__SIGOFFSET; sig < NSIG; sig++) + { +#ifdef NOSIGQUEUE + if (InterlockedExchange (myself->getsigtodo(sig), 0L) > 0) +#else + while (InterlockedDecrement (myself->getsigtodo(sig)) >= 0) +#endif + { + if (sig > 0 && sig != SIGCONT && sig != SIGKILL && sig != SIGSTOP && + (sigismember (& myself->getsigmask (), sig) || + myself->process_state & PID_STOPPED)) + { + sip_printf ("sig %d blocked", sig); + break; + } + + /* Found a signal to process */ + sip_printf ("processing signal %d", sig); + switch (sig) + { + case __SIGFLUSH: + /* just forcing the loop */ + break; + + /* Internal signal to force a flush of strace data to disk. */ + case __SIGSTRACE: + // proc_strace (); // Dump cached strace_printf stuff. + break; + + /* Signalled from a child process that it has stopped */ + case __SIGCHILDSTOPPED: + sip_printf ("Received child stopped notification"); + dispatched |= sig_handle (SIGCHLD); + if (proc_subproc (PROC_CHILDSTOPPED, 0)) + dispatched |= 1; + break; + + /* A normal UNIX signal */ + default: + sip_printf ("Got signal %d", sig); + dispatched |= sig_handle (sig); + goto nextsig; + } + } +#ifndef NOSIGQUEUE + /* Decremented too far. */ + if (InterlockedIncrement (myself->getsigtodo(sig)) > 0) + pending_signals = 1; +#endif + nextsig: + continue; + } + + /* Signal completion of signal handling depending on which semaphore + * woke up the WaitForMultipleObjects above. + */ + switch (rc) + { + case 0: + SetEvent (sigcomplete_main); + break; + case 1: + ReleaseSemaphore (sigcomplete_nonmain, 1, NULL); + break; + default: + /* Signal from another process. No need to synchronize. */ + break; + } + + if (dispatched < 0) + pending_signals = 1; + sip_printf ("looping"); + } + + sip_printf ("done"); + return 0; +} + +/* Wait for subprocesses to terminate. Executes in a separate thread. */ +static DWORD WINAPI +wait_subproc (VOID *arg) +{ + sip_printf ("starting"); + int errloop = 0; + + for (;;) + { + DWORD rc = WaitForMultipleObjects (nchildren + 1, events, FALSE, + proc_loop_wait); + if (rc == WAIT_TIMEOUT) + if (!proc_loop_wait) + break; // Exiting + else + continue; + + if (rc == WAIT_FAILED) + { + if (!proc_loop_wait) + break; + + /* It's ok to get an ERROR_INVALID_HANDLE since another thread may have + closed a handle in the children[] array. So, we try looping a couple + of times to stabilize. FIXME - this is not foolproof. Probably, this + thread should be responsible for closing the children. */ + if (++errloop < 10 && GetLastError () == ERROR_INVALID_HANDLE) + continue; + + system_printf ("wait failed. nchildren %d, wait %d, %E", + nchildren, proc_loop_wait); + + for (int i = 0; i < nchildren + 1; i++) + if ((rc = WaitForSingleObject (events[i], 0)) == WAIT_OBJECT_0 || + rc == WAIT_TIMEOUT) + continue; + else + system_printf ("event[%d] %p, %E", i, events[0]); + break; + } + + errloop = 0; + rc -= WAIT_OBJECT_0; + if (rc-- != 0) + (void)proc_subproc (PROC_CHILDTERMINATED, rc); + sip_printf ("looping"); + } + + ForceCloseHandle (events[0]); + events[0] = NULL; + sip_printf ("done"); + return 0; +} |