1478 lines
39 KiB
C++
1478 lines
39 KiB
C++
/* sigproc.cc: inter/intra signal and sub process handler
|
|
|
|
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 "winsup.h"
|
|
#include "miscfuncs.h"
|
|
#include <stdlib.h>
|
|
#include <sys/cygwin.h>
|
|
#include "cygerrno.h"
|
|
#include "sigproc.h"
|
|
#include "path.h"
|
|
#include "fhandler.h"
|
|
#include "dtable.h"
|
|
#include "cygheap.h"
|
|
#include "child_info_magic.h"
|
|
#include "shared_info.h"
|
|
#include "cygtls.h"
|
|
#include "ntdll.h"
|
|
#include "exception.h"
|
|
|
|
/*
|
|
* Convenience defines
|
|
*/
|
|
#define WSSC 60000 // Wait for signal completion
|
|
#define WPSP 40000 // Wait for proc_subproc mutex
|
|
|
|
/*
|
|
* Global variables
|
|
*/
|
|
struct sigaction *global_sigs;
|
|
|
|
const char *__sp_fn ;
|
|
int __sp_ln;
|
|
|
|
bool no_thread_exit_protect::flag;
|
|
|
|
/* Flag to sig_send that signal goes to current process but no wait is
|
|
required. */
|
|
char NO_COPY myself_nowait_dummy[1] = {'0'};
|
|
|
|
#define Static static NO_COPY
|
|
|
|
/* All my children info. Avoid expensive constructor ops at DLL
|
|
startup.
|
|
|
|
This class can allocate memory. But there's no need to free it
|
|
because only one instance of the class is created per process. */
|
|
class child_procs {
|
|
#ifdef __i386__
|
|
static const int _NPROCS = 256;
|
|
static const int _NPROCS_2 = 1023;
|
|
#else
|
|
static const int _NPROCS = 1024;
|
|
static const int _NPROCS_2 = 4095;
|
|
#endif
|
|
int _count;
|
|
uint8_t _procs[_NPROCS * sizeof (pinfo)] __attribute__ ((__aligned__));
|
|
pinfo *_procs_2;
|
|
public:
|
|
int count () const { return _count; }
|
|
int add_one () { return ++_count; }
|
|
int del_one () { return --_count; }
|
|
int reset () { return _count = 0; }
|
|
pinfo &operator[] (int idx)
|
|
{
|
|
if (idx >= _NPROCS)
|
|
{
|
|
if (!_procs_2)
|
|
{
|
|
/* Use HeapAlloc to avoid propagating this memory area
|
|
to the child processes. */
|
|
_procs_2 = (pinfo *) HeapAlloc (GetProcessHeap (),
|
|
HEAP_GENERATE_EXCEPTIONS
|
|
| HEAP_ZERO_MEMORY,
|
|
(_NPROCS_2 + 1) * sizeof (pinfo));
|
|
}
|
|
return _procs_2[idx - _NPROCS];
|
|
}
|
|
return ((pinfo *) _procs)[idx];
|
|
}
|
|
int max_child_procs () const { return _NPROCS + _NPROCS_2; }
|
|
};
|
|
Static child_procs chld_procs;
|
|
|
|
/* Start of queue for waiting threads. */
|
|
Static waitq waitq_head;
|
|
|
|
/* Controls access to subproc stuff. */
|
|
Static muto sync_proc_subproc;
|
|
|
|
_cygtls NO_COPY *_sig_tls;
|
|
|
|
Static HANDLE my_sendsig;
|
|
Static HANDLE my_readsig;
|
|
|
|
/* Used in select if a signalfd is part of the read descriptor set */
|
|
HANDLE NO_COPY my_pendingsigs_evt;
|
|
|
|
/* Function declarations */
|
|
static int __reg1 checkstate (waitq *);
|
|
static __inline__ bool get_proc_lock (DWORD, DWORD);
|
|
static int remove_proc (int);
|
|
static bool stopped_or_terminated (waitq *, _pinfo *);
|
|
static void WINAPI wait_sig (VOID *arg);
|
|
|
|
/* wait_sig bookkeeping */
|
|
|
|
class pending_signals
|
|
{
|
|
sigpacket sigs[_NSIG + 1];
|
|
sigpacket start;
|
|
bool retry;
|
|
|
|
public:
|
|
void add (sigpacket&);
|
|
bool pending () {retry = true; return !!start.next;}
|
|
void clear (int sig) {sigs[sig].si.si_signo = 0;}
|
|
void clear (_cygtls *tls);
|
|
friend void __reg1 sig_dispatch_pending (bool);
|
|
friend void WINAPI wait_sig (VOID *arg);
|
|
};
|
|
|
|
Static pending_signals sigq;
|
|
|
|
/* Functions */
|
|
void
|
|
sigalloc ()
|
|
{
|
|
cygheap->sigs = global_sigs =
|
|
(struct sigaction *) ccalloc_abort (HEAP_SIGS, _NSIG, sizeof (struct sigaction));
|
|
global_sigs[SIGSTOP].sa_flags = SA_RESTART | SA_NODEFER;
|
|
}
|
|
|
|
void
|
|
signal_fixup_after_exec ()
|
|
{
|
|
global_sigs = cygheap->sigs;
|
|
/* Set up child's signal handlers */
|
|
for (int i = 0; i < _NSIG; i++)
|
|
{
|
|
global_sigs[i].sa_mask = 0;
|
|
if (global_sigs[i].sa_handler != SIG_IGN)
|
|
{
|
|
global_sigs[i].sa_handler = SIG_DFL;
|
|
global_sigs[i].sa_flags &= ~ SA_SIGINFO;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Get the sync_proc_subproc muto to control access to
|
|
* children, proc arrays.
|
|
* Attempt to handle case where process is exiting as we try to grab
|
|
* the mutex.
|
|
*/
|
|
static bool
|
|
get_proc_lock (DWORD what, DWORD val)
|
|
{
|
|
if (!cygwin_finished_initializing)
|
|
return true;
|
|
Static int lastwhat = -1;
|
|
if (!sync_proc_subproc)
|
|
{
|
|
sigproc_printf ("sync_proc_subproc is NULL");
|
|
return false;
|
|
}
|
|
if (sync_proc_subproc.acquire (WPSP))
|
|
{
|
|
lastwhat = what;
|
|
return true;
|
|
}
|
|
system_printf ("Couldn't acquire %s for(%d,%d), last %d, %E",
|
|
sync_proc_subproc.name, what, val, lastwhat);
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
proc_can_be_signalled (_pinfo *p)
|
|
{
|
|
if (!(p->exitcode & EXITCODE_SET))
|
|
{
|
|
if (ISSTATE (p, PID_INITIALIZING) ||
|
|
(((p)->process_state & (PID_ACTIVE | PID_IN_USE)) ==
|
|
(PID_ACTIVE | PID_IN_USE)))
|
|
return true;
|
|
}
|
|
|
|
set_errno (ESRCH);
|
|
return false;
|
|
}
|
|
|
|
bool __reg1
|
|
pid_exists (pid_t pid)
|
|
{
|
|
pinfo p (pid);
|
|
return p && p->exists ();
|
|
}
|
|
|
|
/* Return true if this is one of our children, false otherwise. */
|
|
static inline bool
|
|
mychild (int pid)
|
|
{
|
|
for (int i = 0; i < chld_procs.count (); i++)
|
|
if (chld_procs[i]->pid == pid)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Handle all subprocess requests
|
|
*/
|
|
int __reg2
|
|
proc_subproc (DWORD what, uintptr_t val)
|
|
{
|
|
int slot;
|
|
int rc = 1;
|
|
int potential_match;
|
|
int clearing;
|
|
waitq *w;
|
|
|
|
#define wval ((waitq *) val)
|
|
#define vchild (*((pinfo *) val))
|
|
|
|
sigproc_printf ("args: %x, %d", what, val);
|
|
|
|
if (!get_proc_lock (what, val)) // Serialize access to this function
|
|
{
|
|
system_printf ("couldn't get proc lock. what %d, val %d", what, val);
|
|
goto out1;
|
|
}
|
|
|
|
switch (what)
|
|
{
|
|
/* Add a new subprocess to the children arrays.
|
|
* (usually called from the main thread)
|
|
*/
|
|
case PROC_ADD_CHILD:
|
|
/* Filled up process table? */
|
|
if (chld_procs.count () >= chld_procs.max_child_procs ())
|
|
{
|
|
sigproc_printf ("proc table overflow: hit %d processes, pid %d\n",
|
|
chld_procs.count (), vchild->pid);
|
|
rc = 0;
|
|
set_errno (EAGAIN);
|
|
break;
|
|
}
|
|
|
|
if (vchild != myself)
|
|
{
|
|
vchild->uid = myself->uid;
|
|
vchild->gid = myself->gid;
|
|
vchild->pgid = myself->pgid;
|
|
vchild->sid = myself->sid;
|
|
vchild->ctty = myself->ctty;
|
|
vchild->cygstarted = true;
|
|
vchild->process_state |= PID_INITIALIZING;
|
|
vchild->ppid = myself->pid; /* always set last */
|
|
}
|
|
break;
|
|
|
|
case PROC_ATTACH_CHILD:
|
|
slot = chld_procs.count ();
|
|
chld_procs[slot] = vchild;
|
|
rc = chld_procs[slot].wait ();
|
|
if (rc)
|
|
{
|
|
sigproc_printf ("added pid %d to proc table, slot %d", vchild->pid,
|
|
slot);
|
|
chld_procs.add_one ();
|
|
}
|
|
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 != -1 && wval->pid && !mychild (wval->pid))
|
|
goto out; // invalid pid. flag no such child
|
|
|
|
wval->status = 0; // Don't know status yet
|
|
sigproc_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);
|
|
ProtectHandle1 (wval->ev, wq_ev);
|
|
}
|
|
|
|
ResetEvent (wval->ev);
|
|
w = waitq_head.next;
|
|
waitq_head.next = wval; /* Add at the beginning. */
|
|
wval->next = w; /* Link in rest of the list. */
|
|
clearing = false;
|
|
goto scan_wait;
|
|
|
|
case PROC_EXEC_CLEANUP:
|
|
/* Cleanup backwards to eliminate redundant copying of chld_procs
|
|
array members inside remove_proc. */
|
|
while (chld_procs.count ())
|
|
remove_proc (chld_procs.count () - 1);
|
|
for (w = &waitq_head; w->next != NULL; w = w->next)
|
|
CloseHandle (w->next->ev);
|
|
break;
|
|
|
|
/* 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. */
|
|
if (val)
|
|
sigproc_printf ("clear waiting threads");
|
|
else
|
|
sigproc_printf ("looking for processes to reap, count %d",
|
|
chld_procs.count ());
|
|
clearing = val;
|
|
|
|
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)
|
|
sigproc_printf ("released waiting thread");
|
|
else if (!clearing && !(w->next->options & WNOHANG) && potential_match < 0)
|
|
sigproc_printf ("only found non-terminated children");
|
|
else if (potential_match <= 0) // nothing matched
|
|
{
|
|
sigproc_printf ("waiting thread found no children");
|
|
HANDLE oldw = w->next->ev;
|
|
w->next->pid = 0;
|
|
if (clearing)
|
|
w->next->status = -1; /* flag that a signal was received */
|
|
else if (!potential_match || !(w->next->options & WNOHANG))
|
|
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;
|
|
}
|
|
|
|
if (!clearing)
|
|
sigproc_printf ("finished processing terminated/stopped child");
|
|
else
|
|
{
|
|
waitq_head.next = NULL;
|
|
sigproc_printf ("finished clearing");
|
|
}
|
|
|
|
if (global_sigs[SIGCHLD].sa_handler == (void *) SIG_IGN)
|
|
for (int i = 0; i < chld_procs.count (); i += remove_proc (i))
|
|
continue;
|
|
}
|
|
|
|
out:
|
|
sync_proc_subproc.release (); // Release the lock
|
|
out1:
|
|
sigproc_printf ("returning %d", rc);
|
|
return rc;
|
|
#undef wval
|
|
#undef vchild
|
|
}
|
|
|
|
// FIXME: This is inelegant
|
|
void
|
|
_cygtls::remove_wq (DWORD wait)
|
|
{
|
|
if (wq.thread_ev)
|
|
{
|
|
if (exit_state < ES_FINAL && waitq_head.next && sync_proc_subproc
|
|
&& sync_proc_subproc.acquire (wait))
|
|
{
|
|
ForceCloseHandle1 (wq.thread_ev, wq_ev);
|
|
wq.thread_ev = NULL;
|
|
for (waitq *w = &waitq_head; w->next != NULL; w = w->next)
|
|
if (w->next == &wq)
|
|
{
|
|
w->next = wq.next;
|
|
break;
|
|
}
|
|
sync_proc_subproc.release ();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* 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 chld_procs. */
|
|
void
|
|
proc_terminate ()
|
|
{
|
|
sigproc_printf ("child_procs count %d", chld_procs.count ());
|
|
if (chld_procs.count ())
|
|
{
|
|
sync_proc_subproc.acquire (WPSP);
|
|
|
|
proc_subproc (PROC_CLEARWAIT, 1);
|
|
|
|
/* Clean out proc processes from the pid list. */
|
|
for (int i = 0; i < chld_procs.count (); i++)
|
|
{
|
|
/* If we've execed then the execed process will handle setting ppid
|
|
to 1 iff it is a Cygwin process. */
|
|
if (!have_execed || !have_execed_cygwin)
|
|
chld_procs[i]->ppid = 1;
|
|
if (chld_procs[i].wait_thread)
|
|
chld_procs[i].wait_thread->terminate_thread ();
|
|
/* Release memory associated with this process unless it is 'myself'.
|
|
'myself' is only in the chld_procs table when we've execed. We
|
|
reach here when the next process has finished initializing but we
|
|
still can't free the memory used by 'myself' since it is used
|
|
later on during cygwin tear down. */
|
|
if (chld_procs[i] != myself)
|
|
chld_procs[i].release ();
|
|
}
|
|
chld_procs.reset ();
|
|
sync_proc_subproc.release ();
|
|
}
|
|
sigproc_printf ("leaving");
|
|
}
|
|
|
|
/* Clear pending signal */
|
|
void __reg1
|
|
sig_clear (int sig)
|
|
{
|
|
sigq.clear (sig);
|
|
}
|
|
|
|
/* Clear pending signals of specific thread. Called under TLS lock from
|
|
_cygtls::remove_pending_sigs. */
|
|
void
|
|
pending_signals::clear (_cygtls *tls)
|
|
{
|
|
sigpacket *q = &start, *qnext;
|
|
|
|
while ((qnext = q->next))
|
|
if (qnext->sigtls == tls)
|
|
{
|
|
qnext->si.si_signo = 0;
|
|
q->next = qnext->next;
|
|
}
|
|
else
|
|
q = qnext;
|
|
}
|
|
|
|
/* Clear pending signals of specific thread. Called from _cygtls::remove */
|
|
void
|
|
_cygtls::remove_pending_sigs ()
|
|
{
|
|
sigq.clear (this);
|
|
}
|
|
|
|
extern "C" int
|
|
sigpending (sigset_t *mask)
|
|
{
|
|
sigset_t outset = sig_send (myself, __SIGPENDING, &_my_tls);
|
|
if (outset == SIG_BAD_MASK)
|
|
return -1;
|
|
*mask = outset;
|
|
return 0;
|
|
}
|
|
|
|
/* Force the wait_sig thread to wake up and scan for pending signals */
|
|
void __reg1
|
|
sig_dispatch_pending (bool fast)
|
|
{
|
|
/* Non-atomically test for any signals pending and wake up wait_sig if any are
|
|
found. It's ok if there's a race here since the next call to this function
|
|
should catch it. */
|
|
if (sigq.pending () && &_my_tls != _sig_tls)
|
|
sig_send (myself, fast ? __SIGFLUSHFAST : __SIGFLUSH);
|
|
}
|
|
|
|
/* Signal thread initialization. Called from dll_crt0_1.
|
|
This routine starts the signal handling thread. */
|
|
void
|
|
sigproc_init ()
|
|
{
|
|
char char_sa_buf[1024];
|
|
PSECURITY_ATTRIBUTES sa = sec_user_nih ((PSECURITY_ATTRIBUTES) char_sa_buf, cygheap->user.sid());
|
|
DWORD err = fhandler_pipe::create (sa, &my_readsig, &my_sendsig,
|
|
_NSIG * sizeof (sigpacket), "sigwait",
|
|
PIPE_ADD_PID);
|
|
if (err)
|
|
{
|
|
SetLastError (err);
|
|
api_fatal ("couldn't create signal pipe, %E");
|
|
}
|
|
ProtectHandle (my_readsig);
|
|
myself->sendsig = my_sendsig;
|
|
my_pendingsigs_evt = CreateEvent (NULL, TRUE, FALSE, NULL);
|
|
if (!my_pendingsigs_evt)
|
|
api_fatal ("couldn't create pending signal event, %E");
|
|
|
|
/* sync_proc_subproc is used by proc_subproc. It serializes
|
|
access to the children and proc arrays. */
|
|
sync_proc_subproc.init ("sync_proc_subproc");
|
|
new cygthread (wait_sig, cygself, "sig");
|
|
}
|
|
|
|
/* Exit the current thread very carefully.
|
|
See cgf-000017 in DevNotes for more details on why this is
|
|
necessary. */
|
|
void
|
|
exit_thread (DWORD res)
|
|
{
|
|
# undef ExitThread
|
|
if (no_thread_exit_protect ())
|
|
ExitThread (res);
|
|
sigfillset (&_my_tls.sigmask); /* No signals wanted */
|
|
|
|
/* CV 2014-11-21: Disable the code sending a signal. The problem with
|
|
this code is that it allows deadlocks under signal-rich multithreading
|
|
conditions.
|
|
The original problem reported in 2012 couldn't be reproduced anymore,
|
|
even disabling this code. Tested on XP 32, Vista 32, W7 32, WOW64, 64,
|
|
W8.1 WOW64, 64. */
|
|
#if 0
|
|
lock_process for_now; /* May block indefinitely when exiting. */
|
|
HANDLE h;
|
|
if (!DuplicateHandle (GetCurrentProcess (), GetCurrentThread (),
|
|
GetCurrentProcess (), &h,
|
|
0, FALSE, DUPLICATE_SAME_ACCESS))
|
|
{
|
|
#ifdef DEBUGGING
|
|
system_printf ("couldn't duplicate the current thread, %E");
|
|
#endif
|
|
for_now.release ();
|
|
ExitThread (res);
|
|
}
|
|
ProtectHandle1 (h, exit_thread);
|
|
/* Tell wait_sig to wait for this thread to exit. It can then release
|
|
the lock below and close the above-opened handle. */
|
|
siginfo_t si = {__SIGTHREADEXIT, SI_KERNEL};
|
|
si.si_cyg = h;
|
|
sig_send (myself_nowait, si, &_my_tls);
|
|
#endif
|
|
ExitThread (res);
|
|
}
|
|
|
|
sigset_t __reg3
|
|
sig_send (_pinfo *p, int sig, _cygtls *tls)
|
|
{
|
|
siginfo_t si = {};
|
|
si.si_signo = sig;
|
|
si.si_code = SI_KERNEL;
|
|
return sig_send (p, si, tls);
|
|
}
|
|
|
|
/* 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. */
|
|
sigset_t __reg3
|
|
sig_send (_pinfo *p, siginfo_t& si, _cygtls *tls)
|
|
{
|
|
int rc = 1;
|
|
bool its_me;
|
|
HANDLE sendsig;
|
|
sigpacket pack;
|
|
bool communing = si.si_signo == __SIGCOMMUNE;
|
|
|
|
pack.wakeup = NULL;
|
|
bool wait_for_completion;
|
|
if (!(its_me = p == NULL || p == myself || p == myself_nowait))
|
|
{
|
|
/* 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? */
|
|
{
|
|
sigproc_printf ("invalid pid %d(%x), signal %d",
|
|
p->pid, p->process_state, si.si_signo);
|
|
goto out;
|
|
}
|
|
wait_for_completion = false;
|
|
}
|
|
else
|
|
{
|
|
wait_for_completion = p != myself_nowait;
|
|
p = myself;
|
|
}
|
|
|
|
|
|
if (its_me)
|
|
sendsig = my_sendsig;
|
|
else
|
|
{
|
|
HANDLE dupsig;
|
|
DWORD dwProcessId;
|
|
for (int i = 0; !p->sendsig && i < 10000; i++)
|
|
yield ();
|
|
if (p->sendsig)
|
|
{
|
|
dupsig = p->sendsig;
|
|
dwProcessId = p->dwProcessId;
|
|
}
|
|
else
|
|
{
|
|
dupsig = p->exec_sendsig;
|
|
dwProcessId = p->exec_dwProcessId;
|
|
}
|
|
if (!dupsig)
|
|
{
|
|
set_errno (EAGAIN);
|
|
sigproc_printf ("sendsig handle never materialized");
|
|
goto out;
|
|
}
|
|
HANDLE hp = OpenProcess (PROCESS_DUP_HANDLE, false, dwProcessId);
|
|
if (!hp)
|
|
{
|
|
__seterrno ();
|
|
sigproc_printf ("OpenProcess failed, %E");
|
|
goto out;
|
|
}
|
|
VerifyHandle (hp);
|
|
if (!DuplicateHandle (hp, dupsig, GetCurrentProcess (), &sendsig, 0,
|
|
false, DUPLICATE_SAME_ACCESS) || !sendsig)
|
|
{
|
|
__seterrno ();
|
|
sigproc_printf ("DuplicateHandle failed, %E");
|
|
CloseHandle (hp);
|
|
goto out;
|
|
}
|
|
VerifyHandle (sendsig);
|
|
if (!communing)
|
|
{
|
|
CloseHandle (hp);
|
|
DWORD flag = PIPE_NOWAIT;
|
|
/* Set PIPE_NOWAIT here to avoid blocking when sending a signal.
|
|
(Yes, I know MSDN says not to use this)
|
|
We can't ever block here because it causes a deadlock when
|
|
debugging with gdb. */
|
|
BOOL res = SetNamedPipeHandleState (sendsig, &flag, NULL, NULL);
|
|
sigproc_printf ("%d = SetNamedPipeHandleState (%y, PIPE_NOWAIT, NULL, NULL)", res, sendsig);
|
|
}
|
|
else
|
|
{
|
|
si._si_commune._si_process_handle = hp;
|
|
|
|
HANDLE& tome = si._si_commune._si_write_handle;
|
|
HANDLE& fromthem = si._si_commune._si_read_handle;
|
|
if (!CreatePipeOverlapped (&fromthem, &tome, &sec_all_nih))
|
|
{
|
|
sigproc_printf ("CreatePipe for __SIGCOMMUNE failed, %E");
|
|
__seterrno ();
|
|
goto out;
|
|
}
|
|
if (!DuplicateHandle (GetCurrentProcess (), tome, hp, &tome, 0, false,
|
|
DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
|
|
{
|
|
sigproc_printf ("DuplicateHandle for __SIGCOMMUNE failed, %E");
|
|
__seterrno ();
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
sigproc_printf ("sendsig %p, pid %d, signal %d, its_me %d", sendsig, p->pid,
|
|
si.si_signo, its_me);
|
|
|
|
sigset_t pending;
|
|
if (!its_me)
|
|
pack.mask = NULL;
|
|
else if (si.si_signo == __SIGPENDING)
|
|
pack.mask = &pending;
|
|
else if (si.si_signo == __SIGFLUSH || si.si_signo > 0)
|
|
{
|
|
threadlist_t *tl_entry = cygheap->find_tls (tls ? tls : _main_tls);
|
|
pack.mask = tls ? &tls->sigmask : &_main_tls->sigmask;
|
|
cygheap->unlock_tls (tl_entry);
|
|
}
|
|
else
|
|
pack.mask = NULL;
|
|
|
|
pack.si = si;
|
|
if (!pack.si.si_pid)
|
|
pack.si.si_pid = myself->pid;
|
|
if (!pack.si.si_uid)
|
|
pack.si.si_uid = myself->uid;
|
|
pack.pid = myself->pid;
|
|
pack.sigtls = tls;
|
|
if (wait_for_completion)
|
|
{
|
|
pack.wakeup = CreateEvent (&sec_none_nih, FALSE, FALSE, NULL);
|
|
sigproc_printf ("wakeup %p", pack.wakeup);
|
|
ProtectHandle (pack.wakeup);
|
|
}
|
|
|
|
char *leader;
|
|
size_t packsize;
|
|
if (!communing || !(si._si_commune._si_code & PICOM_EXTRASTR))
|
|
{
|
|
leader = (char *) &pack;
|
|
packsize = sizeof (pack);
|
|
}
|
|
else
|
|
{
|
|
size_t n = strlen (si._si_commune._si_str);
|
|
packsize = sizeof (pack) + sizeof (n) + n;
|
|
char *p = leader = (char *) alloca (packsize);
|
|
memcpy (p, &pack, sizeof (pack)); p += sizeof (pack);
|
|
memcpy (p, &n, sizeof (n)); p += sizeof (n);
|
|
memcpy (p, si._si_commune._si_str, n); p += n;
|
|
}
|
|
|
|
DWORD nb;
|
|
BOOL res;
|
|
/* Try multiple times to send if packsize != nb since that probably
|
|
means that the pipe buffer is full. */
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
res = WriteFile (sendsig, leader, packsize, &nb, NULL);
|
|
if (!res || packsize == nb)
|
|
break;
|
|
Sleep (10);
|
|
res = 0;
|
|
}
|
|
|
|
if (!res)
|
|
{
|
|
/* Couldn't send to the pipe. This probably means that the
|
|
process is exiting. */
|
|
if (!its_me)
|
|
{
|
|
sigproc_printf ("WriteFile for pipe %p failed, %E", sendsig);
|
|
ForceCloseHandle (sendsig);
|
|
}
|
|
else if (!p->exec_sendsig && !exit_state)
|
|
system_printf ("error sending signal %d, pid %u, pipe handle %p, nb %u, packsize %u, %E",
|
|
si.si_signo, p->pid, sendsig, nb, packsize);
|
|
if (GetLastError () == ERROR_BROKEN_PIPE)
|
|
set_errno (ESRCH);
|
|
else
|
|
__seterrno ();
|
|
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)
|
|
{
|
|
sigproc_printf ("Waiting for pack.wakeup %p", pack.wakeup);
|
|
rc = WaitForSingleObject (pack.wakeup, WSSC);
|
|
ForceCloseHandle (pack.wakeup);
|
|
}
|
|
else
|
|
{
|
|
rc = WAIT_OBJECT_0;
|
|
sigproc_printf ("Not waiting for sigcomplete. its_me %d signal %d",
|
|
its_me, si.si_signo);
|
|
if (!its_me)
|
|
ForceCloseHandle (sendsig);
|
|
}
|
|
|
|
pack.wakeup = NULL;
|
|
if (rc == WAIT_OBJECT_0)
|
|
rc = 0; // Successful exit
|
|
else
|
|
{
|
|
set_errno (ENOSYS);
|
|
rc = -1;
|
|
}
|
|
|
|
if (wait_for_completion && si.si_signo != __SIGFLUSHFAST)
|
|
_my_tls.call_signal_handler ();
|
|
|
|
out:
|
|
if (communing && rc)
|
|
{
|
|
if (si._si_commune._si_process_handle)
|
|
CloseHandle (si._si_commune._si_process_handle);
|
|
if (si._si_commune._si_read_handle)
|
|
CloseHandle (si._si_commune._si_read_handle);
|
|
}
|
|
if (pack.wakeup)
|
|
ForceCloseHandle (pack.wakeup);
|
|
if (si.si_signo != __SIGPENDING)
|
|
/* nothing */;
|
|
else if (!rc)
|
|
rc = pending;
|
|
else
|
|
rc = SIG_BAD_MASK;
|
|
sigproc_printf ("returning %p from sending signal %d", rc, si.si_signo);
|
|
return rc;
|
|
}
|
|
|
|
int child_info::retry_count = 0;
|
|
|
|
/* Initialize some of the memory block passed to child processes
|
|
by fork/spawn/exec. */
|
|
child_info::child_info (unsigned in_cb, child_info_types chtype,
|
|
bool need_subproc_ready):
|
|
cb (in_cb), intro (PROC_MAGIC_GENERIC), magic (CHILD_INFO_MAGIC),
|
|
type (chtype), cygheap (::cygheap), cygheap_max (::cygheap_max),
|
|
flag (0), retry (child_info::retry_count), rd_proc_pipe (NULL),
|
|
wr_proc_pipe (NULL)
|
|
{
|
|
/* It appears that when running under WOW64 on Vista 64, the first DWORD
|
|
value in the datastructure lpReserved2 is pointing to (msv_count in
|
|
Cygwin), has to reflect the size of that datastructure as used in the
|
|
Microsoft C runtime (a count value, counting the number of elements in
|
|
two subsequent arrays, BYTE[count and HANDLE[count]), even though the C
|
|
runtime isn't used. Otherwise, if msv_count is 0 or too small, the
|
|
datastructure gets overwritten.
|
|
|
|
This seems to be a bug in Vista's WOW64, which apparently copies the
|
|
lpReserved2 datastructure not using the cbReserved2 size information,
|
|
but using the information given in the first DWORD within lpReserved2
|
|
instead. However, it's not clear if a non-0 count doesn't result in
|
|
trying to evaluate the content, so we do this really only for Vista 64.
|
|
|
|
The value is sizeof (child_info_*) / 5 which results in a count which
|
|
covers the full datastructure, plus not more than 4 extra bytes. This
|
|
is ok as long as the child_info structure is cosily stored within a bigger
|
|
datastructure. */
|
|
msv_count = wincap.needs_count_in_si_lpres2 () ? in_cb / 5 : 0;
|
|
|
|
fhandler_union_cb = sizeof (fhandler_union);
|
|
user_h = cygwin_user_h;
|
|
if (strace.active ())
|
|
{
|
|
NTSTATUS status;
|
|
ULONG DebugFlags;
|
|
|
|
/* Only propagate _CI_STRACED to child if strace is actually tracing
|
|
child processes of this process. The undocumented ProcessDebugFlags
|
|
returns 0 if EPROCESS->NoDebugInherit is TRUE, 1 otherwise.
|
|
This avoids a hang when stracing a forking or spawning process
|
|
with the -f flag set to "don't follow fork". */
|
|
status = NtQueryInformationProcess (GetCurrentProcess (),
|
|
ProcessDebugFlags, &DebugFlags,
|
|
sizeof (DebugFlags), NULL);
|
|
if (NT_SUCCESS (status) && DebugFlags)
|
|
flag |= _CI_STRACED;
|
|
}
|
|
if (need_subproc_ready)
|
|
{
|
|
subproc_ready = CreateEvent (&sec_all, FALSE, FALSE, NULL);
|
|
flag |= _CI_ISCYGWIN;
|
|
}
|
|
sigproc_printf ("subproc_ready %p", subproc_ready);
|
|
/* Create an inheritable handle to pass to the child process. This will
|
|
allow the child to copy cygheap etc. from the parent to itself. If
|
|
we're forking, we also need handle duplicate access. */
|
|
parent = NULL;
|
|
DWORD perms = PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ
|
|
| PROCESS_VM_OPERATION | SYNCHRONIZE;
|
|
if (type == _CH_FORK)
|
|
{
|
|
perms |= PROCESS_DUP_HANDLE;
|
|
/* VirtualQueryEx is documented to require PROCESS_QUERY_INFORMATION.
|
|
That's true for Windows 7, but PROCESS_QUERY_LIMITED_INFORMATION
|
|
appears to be sufficient on Windows 8 and later. */
|
|
if (wincap.needs_query_information ())
|
|
perms |= PROCESS_QUERY_INFORMATION;
|
|
}
|
|
|
|
if (!DuplicateHandle (GetCurrentProcess (), GetCurrentProcess (),
|
|
GetCurrentProcess (), &parent, perms, TRUE, 0))
|
|
system_printf ("couldn't create handle to myself for child, %E");
|
|
}
|
|
|
|
child_info::~child_info ()
|
|
{
|
|
cleanup ();
|
|
}
|
|
|
|
child_info_fork::child_info_fork () :
|
|
child_info (sizeof *this, _CH_FORK, true),
|
|
forker_finished (NULL)
|
|
{
|
|
}
|
|
|
|
child_info_spawn::child_info_spawn (child_info_types chtype, bool need_subproc_ready) :
|
|
child_info (sizeof *this, chtype, need_subproc_ready)
|
|
{
|
|
if (type == _CH_EXEC)
|
|
{
|
|
hExeced = NULL;
|
|
if (my_wr_proc_pipe)
|
|
ev = NULL;
|
|
else if (!(ev = CreateEvent (&sec_none_nih, false, false, NULL)))
|
|
api_fatal ("couldn't create signalling event for exec, %E");
|
|
|
|
get_proc_lock (PROC_EXECING, 0);
|
|
/* exit with lock held */
|
|
}
|
|
}
|
|
|
|
cygheap_exec_info *
|
|
cygheap_exec_info::alloc ()
|
|
{
|
|
cygheap_exec_info *res =
|
|
(cygheap_exec_info *) ccalloc_abort (HEAP_1_EXEC, 1,
|
|
sizeof (cygheap_exec_info)
|
|
+ (chld_procs.count ()
|
|
* sizeof (children[0])));
|
|
res->sigmask = _my_tls.sigmask;
|
|
return res;
|
|
}
|
|
|
|
void
|
|
child_info_spawn::wait_for_myself ()
|
|
{
|
|
postfork (myself);
|
|
if (myself.remember ())
|
|
myself.attach ();
|
|
WaitForSingleObject (ev, INFINITE);
|
|
}
|
|
|
|
void
|
|
child_info::cleanup ()
|
|
{
|
|
if (subproc_ready)
|
|
{
|
|
CloseHandle (subproc_ready);
|
|
subproc_ready = NULL;
|
|
}
|
|
if (parent)
|
|
{
|
|
CloseHandle (parent);
|
|
parent = NULL;
|
|
}
|
|
if (rd_proc_pipe)
|
|
{
|
|
ForceCloseHandle (rd_proc_pipe);
|
|
rd_proc_pipe = NULL;
|
|
}
|
|
if (wr_proc_pipe)
|
|
{
|
|
ForceCloseHandle (wr_proc_pipe);
|
|
wr_proc_pipe = NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
child_info_spawn::cleanup ()
|
|
{
|
|
if (moreinfo)
|
|
{
|
|
if (moreinfo->envp)
|
|
{
|
|
for (char **e = moreinfo->envp; *e; e++)
|
|
cfree (*e);
|
|
cfree (moreinfo->envp);
|
|
}
|
|
if (type != _CH_SPAWN && moreinfo->myself_pinfo)
|
|
CloseHandle (moreinfo->myself_pinfo);
|
|
cfree (moreinfo);
|
|
}
|
|
moreinfo = NULL;
|
|
if (ev)
|
|
{
|
|
CloseHandle (ev);
|
|
ev = NULL;
|
|
}
|
|
if (type == _CH_EXEC)
|
|
{
|
|
if (iscygwin () && hExeced)
|
|
proc_subproc (PROC_EXEC_CLEANUP, 0);
|
|
sync_proc_subproc.release ();
|
|
}
|
|
type = _CH_NADA;
|
|
child_info::cleanup ();
|
|
}
|
|
|
|
/* Record any non-reaped subprocesses to be passed to about-to-be-execed
|
|
process. FIXME: There is a race here if the process exits while we
|
|
are recording it. */
|
|
inline void
|
|
cygheap_exec_info::record_children ()
|
|
{
|
|
for (nchildren = 0; nchildren < chld_procs.count (); nchildren++)
|
|
{
|
|
children[nchildren].pid = chld_procs[nchildren]->pid;
|
|
children[nchildren].p = chld_procs[nchildren];
|
|
/* Set inheritance of required child handles for reattach_children
|
|
in the about-to-be-execed process. */
|
|
children[nchildren].p.set_inheritance (true);
|
|
}
|
|
}
|
|
|
|
void
|
|
child_info_spawn::record_children ()
|
|
{
|
|
if (type == _CH_EXEC && iscygwin ())
|
|
moreinfo->record_children ();
|
|
}
|
|
|
|
/* Reattach non-reaped subprocesses passed in from the cygwin process
|
|
which previously operated under this pid. FIXME: Is there a race here
|
|
if the process exits during cygwin's exec handoff? */
|
|
inline void
|
|
cygheap_exec_info::reattach_children (HANDLE parent)
|
|
{
|
|
for (int i = 0; i < nchildren; i++)
|
|
{
|
|
pinfo p (parent, children[i].p, children[i].pid);
|
|
if (!p)
|
|
debug_only_printf ("couldn't reattach child %d from previous process", children[i].pid);
|
|
else if (!p.attach ())
|
|
debug_only_printf ("attach of child process %d failed", children[i].pid);
|
|
else
|
|
debug_only_printf ("reattached pid %d<%u>, process handle %p, rd_proc_pipe %p->%p",
|
|
p->pid, p->dwProcessId, p.hProcess,
|
|
children[i].p.rd_proc_pipe, p.rd_proc_pipe);
|
|
}
|
|
}
|
|
|
|
void
|
|
child_info_spawn::reattach_children ()
|
|
{
|
|
moreinfo->reattach_children (parent);
|
|
}
|
|
|
|
void
|
|
child_info::ready (bool execed)
|
|
{
|
|
if (!subproc_ready)
|
|
{
|
|
sigproc_printf ("subproc_ready not set");
|
|
return;
|
|
}
|
|
|
|
if (dynamically_loaded)
|
|
sigproc_printf ("not really ready");
|
|
else if (!SetEvent (subproc_ready))
|
|
api_fatal ("SetEvent failed, %E");
|
|
else
|
|
sigproc_printf ("signalled %p that I was ready", subproc_ready);
|
|
|
|
if (execed)
|
|
{
|
|
CloseHandle (subproc_ready);
|
|
subproc_ready = NULL;
|
|
}
|
|
}
|
|
|
|
bool
|
|
child_info::sync (pid_t pid, HANDLE& hProcess, DWORD howlong)
|
|
{
|
|
bool res;
|
|
HANDLE w4[2];
|
|
unsigned n = 0;
|
|
unsigned nsubproc_ready;
|
|
|
|
if (!subproc_ready)
|
|
nsubproc_ready = WAIT_OBJECT_0 + 3;
|
|
else
|
|
{
|
|
w4[n++] = subproc_ready;
|
|
nsubproc_ready = 0;
|
|
}
|
|
w4[n++] = hProcess;
|
|
|
|
sigproc_printf ("n %d, waiting for subproc_ready(%p) and child process(%p)", n, w4[0], w4[1]);
|
|
DWORD x = WaitForMultipleObjects (n, w4, FALSE, howlong);
|
|
x -= WAIT_OBJECT_0;
|
|
if (x >= n)
|
|
{
|
|
system_printf ("wait failed, pid %u, %E", pid);
|
|
res = false;
|
|
}
|
|
else
|
|
{
|
|
if (x != nsubproc_ready)
|
|
{
|
|
res = false;
|
|
GetExitCodeProcess (hProcess, &exit_code);
|
|
}
|
|
else
|
|
{
|
|
res = true;
|
|
exit_code = STILL_ACTIVE;
|
|
if (type == _CH_EXEC && my_wr_proc_pipe)
|
|
{
|
|
ForceCloseHandle1 (hProcess, childhProc);
|
|
hProcess = NULL;
|
|
}
|
|
}
|
|
sigproc_printf ("pid %u, WFMO returned %d, exit_code %y, res %d", pid, x,
|
|
exit_code, res);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
DWORD
|
|
child_info::proc_retry (HANDLE h)
|
|
{
|
|
if (!exit_code)
|
|
return EXITCODE_OK;
|
|
sigproc_printf ("exit_code %y", exit_code);
|
|
switch (exit_code)
|
|
{
|
|
case STILL_ACTIVE: /* shouldn't happen */
|
|
sigproc_printf ("STILL_ACTIVE? How'd we get here?");
|
|
break;
|
|
case STATUS_DLL_NOT_FOUND:
|
|
case STATUS_ACCESS_VIOLATION:
|
|
case STATUS_ILLEGAL_INSTRUCTION:
|
|
case STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION: /* pseudo-reloc.c specific */
|
|
return exit_code;
|
|
case STATUS_CONTROL_C_EXIT:
|
|
if (saw_ctrl_c ())
|
|
return EXITCODE_OK;
|
|
fallthrough;
|
|
case STATUS_DLL_INIT_FAILED:
|
|
case STATUS_DLL_INIT_FAILED_LOGOFF:
|
|
case EXITCODE_RETRY:
|
|
if (retry-- > 0)
|
|
exit_code = 0;
|
|
break;
|
|
case EXITCODE_FORK_FAILED: /* windows prevented us from forking */
|
|
break;
|
|
|
|
/* Count down non-recognized exit codes more quickly since they aren't
|
|
due to known conditions. */
|
|
default:
|
|
if (!iscygwin () && (exit_code & 0xffff0000) != 0xc0000000)
|
|
break;
|
|
if ((retry -= 2) < 0)
|
|
retry = 0;
|
|
else
|
|
exit_code = 0;
|
|
}
|
|
if (!exit_code)
|
|
ForceCloseHandle1 (h, childhProc);
|
|
return exit_code;
|
|
}
|
|
|
|
bool
|
|
child_info_fork::abort (const char *fmt, ...)
|
|
{
|
|
if (fmt)
|
|
{
|
|
va_list ap;
|
|
va_start (ap, fmt);
|
|
if (silentfail ())
|
|
strace_vprintf (DEBUG, fmt, ap);
|
|
else
|
|
strace_vprintf (SYSTEM, fmt, ap);
|
|
TerminateProcess (GetCurrentProcess (), EXITCODE_FORK_FAILED);
|
|
}
|
|
if (retry > 0)
|
|
TerminateProcess (GetCurrentProcess (), EXITCODE_RETRY);
|
|
return false;
|
|
}
|
|
|
|
/* Check the state of all of our children to see if any are stopped or
|
|
* terminated.
|
|
*/
|
|
static int __reg1
|
|
checkstate (waitq *parent_w)
|
|
{
|
|
int potential_match = 0;
|
|
|
|
sigproc_printf ("child_procs count %d", chld_procs.count ());
|
|
|
|
/* Check already dead processes first to see if they match the criteria
|
|
* given in w->next. */
|
|
int res;
|
|
for (int i = 0; i < chld_procs.count (); i++)
|
|
if ((res = stopped_or_terminated (parent_w, chld_procs[i])))
|
|
{
|
|
remove_proc (i);
|
|
potential_match = 1;
|
|
goto out;
|
|
}
|
|
|
|
sigproc_printf ("no matching terminated children found");
|
|
potential_match = -!!chld_procs.count ();
|
|
|
|
out:
|
|
sigproc_printf ("returning %d", potential_match);
|
|
return potential_match;
|
|
}
|
|
|
|
/* Remove a proc from chld_procs by swapping it with the last child in the list.
|
|
Also releases shared memory of exited processes. */
|
|
static int
|
|
remove_proc (int ci)
|
|
{
|
|
if (have_execed)
|
|
{
|
|
if (_my_tls._ctinfo != chld_procs[ci].wait_thread)
|
|
chld_procs[ci].wait_thread->terminate_thread ();
|
|
}
|
|
else if (chld_procs[ci] && chld_procs[ci]->exists ())
|
|
return 1;
|
|
|
|
sigproc_printf ("removing chld_procs[%d], pid %d, child_procs count %d",
|
|
ci, chld_procs[ci]->pid, chld_procs.count ());
|
|
if (chld_procs[ci] != myself)
|
|
chld_procs[ci].release ();
|
|
if (ci < chld_procs.del_one ())
|
|
{
|
|
/* Wait for proc_waiter thread to make a copy of this element before
|
|
moving it or it may become confused. The chances are very high that
|
|
the proc_waiter thread has already done this by the time we
|
|
get here. */
|
|
if (!have_execed && !exit_state)
|
|
while (!chld_procs[chld_procs.count ()].waiter_ready)
|
|
yield ();
|
|
chld_procs[ci] = chld_procs[chld_procs.count ()];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* 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 non-zero if waiting thread released. */
|
|
static bool
|
|
stopped_or_terminated (waitq *parent_w, _pinfo *child)
|
|
{
|
|
int might_match;
|
|
waitq *w = parent_w->next;
|
|
|
|
sigproc_printf ("considering pid %d, pgid %d, w->pid %d", child->pid, child->pgid, w->pid);
|
|
if (w->pid == -1)
|
|
might_match = 1;
|
|
else if (w->pid == 0)
|
|
might_match = child->pgid == myself->pgid;
|
|
else if (w->pid < 0)
|
|
might_match = child->pgid == -w->pid;
|
|
else
|
|
might_match = (w->pid == child->pid);
|
|
|
|
if (!might_match)
|
|
return false;
|
|
|
|
int terminated;
|
|
|
|
if (!((terminated = (child->process_state == PID_EXITED))
|
|
|| ((w->options & WCONTINUED) && child->stopsig == SIGCONT)
|
|
|| ((w->options & WUNTRACED) && child->stopsig && child->stopsig != SIGCONT)))
|
|
return false;
|
|
|
|
parent_w->next = w->next; /* successful wait. remove from wait queue */
|
|
w->pid = child->pid;
|
|
|
|
if (!terminated)
|
|
{
|
|
sigproc_printf ("stopped child, stop signal %d", child->stopsig);
|
|
if (child->stopsig == SIGCONT)
|
|
w->status = __W_CONTINUED;
|
|
else
|
|
w->status = (child->stopsig << 8) | 0x7f;
|
|
child->stopsig = 0;
|
|
}
|
|
else
|
|
{
|
|
child->process_state = PID_REAPED;
|
|
w->status = (__uint16_t) child->exitcode;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
if (!SetEvent (w->ev)) /* wake up wait4 () immediately */
|
|
system_printf ("couldn't wake up wait event %p, %E", w->ev);
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
talktome (siginfo_t *si)
|
|
{
|
|
unsigned size = sizeof (*si);
|
|
sigproc_printf ("pid %d wants some information", si->si_pid);
|
|
if (si->_si_commune._si_code & PICOM_EXTRASTR)
|
|
{
|
|
size_t n;
|
|
DWORD nb;
|
|
if (!ReadFile (my_readsig, &n, sizeof (n), &nb, NULL) || nb != sizeof (n))
|
|
return;
|
|
siginfo_t *newsi = (siginfo_t *) alloca (size += n + 1);
|
|
*newsi = *si;
|
|
newsi->_si_commune._si_str = (char *) (newsi + 1);
|
|
if (!ReadFile (my_readsig, newsi->_si_commune._si_str, n, &nb, NULL) || nb != n)
|
|
return;
|
|
newsi->_si_commune._si_str[n] = '\0';
|
|
si = newsi;
|
|
}
|
|
|
|
pinfo pi (si->si_pid);
|
|
if (pi)
|
|
new cygthread (commune_process, size, si, "commune");
|
|
}
|
|
|
|
/* Add a packet to the beginning of the queue.
|
|
Should only be called from signal thread. */
|
|
void
|
|
pending_signals::add (sigpacket& pack)
|
|
{
|
|
sigpacket *se;
|
|
|
|
se = sigs + pack.si.si_signo;
|
|
if (se->si.si_signo)
|
|
return;
|
|
*se = pack;
|
|
se->next = start.next;
|
|
start.next = se;
|
|
}
|
|
|
|
/* Process signals by waiting for signal data to arrive in a pipe.
|
|
Set a completion event if one was specified. */
|
|
static void WINAPI
|
|
wait_sig (VOID *)
|
|
{
|
|
_sig_tls = &_my_tls;
|
|
bool sig_held = false;
|
|
|
|
sigproc_printf ("entering ReadFile loop, my_readsig %p, my_sendsig %p",
|
|
my_readsig, my_sendsig);
|
|
|
|
hntdll = GetModuleHandle ("ntdll.dll");
|
|
|
|
for (;;)
|
|
{
|
|
DWORD nb;
|
|
sigpacket pack = {};
|
|
if (sigq.retry)
|
|
pack.si.si_signo = __SIGFLUSH;
|
|
else if (!ReadFile (my_readsig, &pack, sizeof (pack), &nb, NULL))
|
|
Sleep (INFINITE); /* Assume were exiting. Never exit this thread */
|
|
else if (nb != sizeof (pack) || !pack.si.si_signo)
|
|
{
|
|
system_printf ("garbled signal pipe data nb %u, sig %d", nb, pack.si.si_signo);
|
|
continue;
|
|
}
|
|
|
|
sigq.retry = false;
|
|
/* Don't process signals when we start exiting */
|
|
if (exit_state > ES_EXIT_STARTING && pack.si.si_signo > 0)
|
|
continue;
|
|
|
|
sigset_t dummy_mask;
|
|
threadlist_t *tl_entry;
|
|
if (!pack.mask)
|
|
{
|
|
tl_entry = cygheap->find_tls (_main_tls);
|
|
dummy_mask = _main_tls->sigmask;
|
|
cygheap->unlock_tls (tl_entry);
|
|
pack.mask = &dummy_mask;
|
|
}
|
|
|
|
sigpacket *q = &sigq.start;
|
|
bool clearwait = false;
|
|
switch (pack.si.si_signo)
|
|
{
|
|
case __SIGCOMMUNE:
|
|
talktome (&pack.si);
|
|
break;
|
|
case __SIGSTRACE:
|
|
strace.activate (false);
|
|
break;
|
|
case __SIGPENDING:
|
|
{
|
|
unsigned bit;
|
|
|
|
*pack.mask = 0;
|
|
tl_entry = cygheap->find_tls (pack.sigtls);
|
|
while ((q = q->next))
|
|
{
|
|
/* Skip thread-specific signals for other threads. */
|
|
if (q->sigtls && pack.sigtls != q->sigtls)
|
|
continue;
|
|
if (pack.sigtls->sigmask & (bit = SIGTOMASK (q->si.si_signo)))
|
|
*pack.mask |= bit;
|
|
}
|
|
cygheap->unlock_tls (tl_entry);
|
|
}
|
|
break;
|
|
case __SIGHOLD:
|
|
sig_held = true;
|
|
break;
|
|
case __SIGSETPGRP:
|
|
init_console_handler (true);
|
|
break;
|
|
case __SIGTHREADEXIT:
|
|
{
|
|
/* Serialize thread exit as the thread exit code can be interpreted
|
|
as the process exit code in some cases when racing with
|
|
ExitProcess/TerminateProcess.
|
|
So, wait for the thread which sent this signal to exit, then
|
|
release the process lock which it held and close it's handle.
|
|
See cgf-000017 in DevNotes for more details.
|
|
*/
|
|
HANDLE h = (HANDLE) pack.si.si_cyg;
|
|
DWORD res = WaitForSingleObject (h, 5000);
|
|
lock_process::force_release (pack.sigtls);
|
|
ForceCloseHandle1 (h, exit_thread);
|
|
if (res != WAIT_OBJECT_0)
|
|
{
|
|
#ifdef DEBUGGING
|
|
try_to_debug();
|
|
#endif
|
|
system_printf ("WaitForSingleObject(%p) for thread exit returned %u", h, res);
|
|
}
|
|
}
|
|
break;
|
|
default: /* Normal (positive) signal */
|
|
if (pack.si.si_signo < 0)
|
|
sig_clear (-pack.si.si_signo);
|
|
else
|
|
sigq.add (pack);
|
|
fallthrough;
|
|
case __SIGNOHOLD:
|
|
sig_held = false;
|
|
fallthrough;
|
|
case __SIGFLUSH:
|
|
case __SIGFLUSHFAST:
|
|
if (!sig_held)
|
|
{
|
|
sigpacket *qnext;
|
|
/* Check the queue for signals. There will always be at least one
|
|
thing on the queue if this was a valid signal. */
|
|
while ((qnext = q->next))
|
|
{
|
|
if (qnext->si.si_signo && qnext->process () <= 0)
|
|
q = qnext;
|
|
else
|
|
{
|
|
q->next = qnext->next;
|
|
qnext->si.si_signo = 0;
|
|
}
|
|
}
|
|
/* At least one signal still queued? The event is used in select
|
|
only, and only to decide if WFMO should wake up in case a
|
|
signalfd is waiting via select/poll for being ready to read a
|
|
pending signal. This method wakes up all threads hanging in
|
|
select and having a signalfd, as soon as a pending signal is
|
|
available, but it's certainly better than constant polling. */
|
|
if (sigq.start.next)
|
|
SetEvent (my_pendingsigs_evt);
|
|
else
|
|
ResetEvent (my_pendingsigs_evt);
|
|
if (pack.si.si_signo == SIGCHLD)
|
|
clearwait = true;
|
|
}
|
|
break;
|
|
}
|
|
if (clearwait && !have_execed)
|
|
proc_subproc (PROC_CLEARWAIT, 0);
|
|
if (pack.wakeup)
|
|
{
|
|
sigproc_printf ("signalling pack.wakeup %p", pack.wakeup);
|
|
SetEvent (pack.wakeup);
|
|
}
|
|
}
|
|
}
|