/* signal.cc Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 Red Hat, Inc. Written by Steve Chamberlain of Cygnus Support, sac@cygnus.com Significant changes by Sergey Okhapkin 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 #include "cygerrno.h" #include #include "pinfo.h" #include "sigproc.h" #include "hires.h" #include "security.h" #include "cygtls.h" int sigcatchers; /* FIXME: Not thread safe. */ #define sigtrapped(func) ((func) != SIG_IGN && (func) != SIG_DFL) static inline void set_sigcatchers (void (*oldsig) (int), void (*cursig) (int)) { #ifdef DEBUGGING int last_sigcatchers = sigcatchers; #endif if (!sigtrapped (oldsig) && sigtrapped (cursig)) sigcatchers++; else if (sigtrapped (oldsig) && !sigtrapped (cursig)) sigcatchers--; #ifdef DEBUGGING if (last_sigcatchers != sigcatchers) sigproc_printf ("last %d, old %d, cur %p, cur %p", last_sigcatchers, sigcatchers, oldsig, cursig); #endif } extern "C" _sig_func_ptr signal (int sig, _sig_func_ptr func) { sig_dispatch_pending (); _sig_func_ptr prev; /* check that sig is in right range */ if (sig < 0 || sig >= NSIG || sig == SIGKILL || sig == SIGSTOP) { set_errno (EINVAL); syscall_printf ("SIG_ERR = signal (%d, %p)", sig, func); return (_sig_func_ptr) SIG_ERR; } prev = global_sigs[sig].sa_handler; global_sigs[sig].sa_handler = func; global_sigs[sig].sa_mask = 0; /* SA_RESTART is set to maintain BSD compatible signal behaviour by default. This is also compatible with the behaviour of signal(2) in Linux. */ global_sigs[sig].sa_flags |= SA_RESTART; set_sigcatchers (prev, func); syscall_printf ("%p = signal (%d, %p)", prev, sig, func); return prev; } extern "C" int nanosleep (const struct timespec *rqtp, struct timespec *rmtp) { int res = 0; sig_dispatch_pending (); pthread_testcancel (); if ((unsigned int) rqtp->tv_sec > (HIRES_DELAY_MAX / 1000 - 1) || (unsigned int) rqtp->tv_nsec > 999999999) { set_errno (EINVAL); return -1; } DWORD resolution = gtod.resolution (); DWORD req = ((rqtp->tv_sec * 1000 + (rqtp->tv_nsec + 999999) / 1000000 + resolution - 1) / resolution) * resolution; DWORD end_time = gtod.dmsecs () + req; syscall_printf ("nanosleep (%ld)", req); int rc = pthread::cancelable_wait (signal_arrived, req); DWORD rem; if ((rem = end_time - gtod.dmsecs ()) > HIRES_DELAY_MAX) rem = 0; if (rc == WAIT_OBJECT_0) { (void) _my_tls.call_signal_handler (); set_errno (EINTR); res = -1; } if (rmtp) { rmtp->tv_sec = rem / 1000; rmtp->tv_nsec = (rem % 1000) * 1000000; } syscall_printf ("%d = nanosleep (%ld, %ld)", res, req, rem); return res; } extern "C" unsigned int sleep (unsigned int seconds) { struct timespec req, rem; req.tv_sec = seconds; req.tv_nsec = 0; nanosleep (&req, &rem); return rem.tv_sec + (rem.tv_nsec > 0); } extern "C" unsigned int usleep (unsigned int useconds) { struct timespec req; req.tv_sec = useconds / 1000000; req.tv_nsec = (useconds % 1000000) * 1000; int res = nanosleep (&req, 0); return res; } extern "C" int sigprocmask (int sig, const sigset_t *set, sigset_t *oldset) { return handle_sigprocmask (sig, set, oldset, myself->getsigmask ()); } int __stdcall handle_sigprocmask (int sig, const sigset_t *set, sigset_t *oldset, sigset_t& opmask) { sig_dispatch_pending (); /* check that sig is in right range */ if (sig < 0 || sig >= NSIG) { set_errno (EINVAL); syscall_printf ("signal %d out of range", sig); return -1; } if (oldset) { if (check_null_invalid_struct_errno (oldset)) return -1; *oldset = opmask; } if (set) { if (check_invalid_read_struct_errno (set)) return -1; sigset_t newmask = opmask; switch (sig) { case SIG_BLOCK: /* add set to current mask */ newmask |= *set; break; case SIG_UNBLOCK: /* remove set from current mask */ newmask &= ~*set; break; case SIG_SETMASK: /* just set it */ newmask = *set; break; default: set_errno (EINVAL); return -1; } (void) set_signal_mask (newmask, opmask); } return 0; } static int kill_worker (pid_t pid, siginfo_t& si) { sig_dispatch_pending (); int res = 0; pinfo dest (pid); bool sendSIGCONT; if (!dest) { set_errno (ESRCH); return -1; } if ((sendSIGCONT = (si.si_signo < 0))) si.si_signo = -si.si_signo; DWORD process_state = dest->process_state; if (si.si_signo == 0) { res = proc_exists (dest) ? 0 : -1; if (res < 0) set_errno (ESRCH); } else if ((res = sig_send (dest, si))) { sigproc_printf ("%d = sig_send, %E ", res); res = -1; } else if (sendSIGCONT) { siginfo_t si2; si2.si_signo = SIGCONT; si2.si_code = SI_KERNEL; si2.si_pid = si2.si_uid = si2.si_errno = 0; (void) sig_send (dest, si2); } syscall_printf ("%d = kill_worker (%d, %d), process_state %p", res, pid, si.si_signo, process_state); return res; } int raise (int sig) { return kill (myself->pid, sig); } static int kill0 (pid_t pid, siginfo_t& si) { syscall_printf ("kill (%d, %d)", pid, si.si_signo); /* check that sig is in right range */ if (si.si_signo < 0 || si.si_signo >= NSIG) { set_errno (EINVAL); syscall_printf ("signal %d out of range", si.si_signo); return -1; } /* Silently ignore stop signals from a member of orphaned process group. FIXME: Why??? */ if (ISSTATE (myself, PID_ORPHANED) && (si.si_signo == SIGTSTP || si.si_signo == SIGTTIN || si.si_signo == SIGTTOU)) si.si_signo = 0; return (pid > 0) ? kill_worker (pid, si) : kill_pgrp (-pid, si); } int killsys (pid_t pid, int sig) { siginfo_t si; si.si_signo = sig; si.si_code = SI_KERNEL; si.si_pid = si.si_uid = si.si_errno = 0; return kill0 (pid, si); } int kill (pid_t pid, int sig) { siginfo_t si; si.si_signo = sig; si.si_code = SI_USER; si.si_pid = si.si_uid = si.si_errno = 0; return kill0 (pid, si); } int kill_pgrp (pid_t pid, siginfo_t& si) { int res = 0; int found = 0; int killself = 0; sigproc_printf ("pid %d, signal %d", pid, si.si_signo); winpids pids ((DWORD) PID_MAP_RW); for (unsigned i = 0; i < pids.npids; i++) { _pinfo *p = pids[i]; if (!proc_exists (p)) continue; /* Is it a process we want to kill? */ if ((pid == 0 && (p->pgid != myself->pgid || p->ctty != myself->ctty)) || (pid > 1 && p->pgid != pid) || (si.si_signo < 0 && NOTSTATE (p, PID_STOPPED))) continue; sigproc_printf ("killing pid %d, pgrp %d, p->ctty %d, myself->ctty %d", p->pid, p->pgid, p->ctty, myself->ctty); if (p == myself) killself++; else if (kill_worker (p->pid, si)) res = -1; found++; } if (killself && !exit_state && kill_worker (myself->pid, si)) res = -1; if (!found) { set_errno (ESRCH); res = -1; } syscall_printf ("%d = kill (%d, %d)", res, pid, si.si_signo); return res; } extern "C" int killpg (pid_t pgrp, int sig) { return kill (-pgrp, sig); } extern "C" void abort (void) { sig_dispatch_pending (); /* Flush all streams as per SUSv2. From my reading of this document, this isn't strictly correct. The streams are supposed to be flushed prior to exit. However, if there is I/O in any signal handler that will not necessarily be flushed. However this is the way FreeBSD does it, and it is much easier to do things this way, so... */ if (_REENT->__cleanup) _REENT->__cleanup (_REENT); /* Ensure that SIGABRT can be caught regardless of blockage. */ sigset_t sig_mask; sigfillset (&sig_mask); sigdelset (&sig_mask, SIGABRT); set_signal_mask (sig_mask); raise (SIGABRT); (void) _my_tls.call_signal_handler (); /* Call any signal handler */ do_exit (1); /* signal handler didn't exit. Goodbye. */ } extern "C" int sigaction (int sig, const struct sigaction *newact, struct sigaction *oldact) { sig_dispatch_pending (); /* check that sig is in right range */ if (sig < 0 || sig >= NSIG) { set_errno (EINVAL); sigproc_printf ("signal %d, newact %p, oldact %p", sig, newact, oldact); syscall_printf ("SIG_ERR = sigaction signal %d out of range", sig); return -1; } struct sigaction oa = global_sigs[sig]; if (newact) sigproc_printf ("signal %d, newact %p (handler %p), oa %p", sig, newact, newact->sa_handler, oa, oa.sa_handler); else sigproc_printf ("signal %d, newact %p, oa %p", sig, newact, oa, oa.sa_handler); if (newact) { if (sig == SIGKILL || sig == SIGSTOP) { set_errno (EINVAL); return -1; } global_sigs[sig] = *newact; if (newact->sa_handler == SIG_IGN) sig_clear (sig); if (newact->sa_handler == SIG_DFL && sig == SIGCHLD) sig_clear (sig); set_sigcatchers (oa.sa_handler, newact->sa_handler); if (sig == SIGCHLD) { myself->process_state &= ~PID_NOCLDSTOP; if (newact->sa_flags & SA_NOCLDSTOP) myself->process_state |= PID_NOCLDSTOP; } } if (oldact) *oldact = oa; return 0; } extern "C" int sigaddset (sigset_t *set, const int sig) { /* check that sig is in right range */ if (sig <= 0 || sig >= NSIG) { set_errno (EINVAL); syscall_printf ("SIG_ERR = sigaddset signal %d out of range", sig); return -1; } *set |= SIGTOMASK (sig); return 0; } extern "C" int sigdelset (sigset_t *set, const int sig) { /* check that sig is in right range */ if (sig <= 0 || sig >= NSIG) { set_errno (EINVAL); syscall_printf ("SIG_ERR = sigdelset signal %d out of range", sig); return -1; } *set &= ~SIGTOMASK (sig); return 0; } extern "C" int sigismember (const sigset_t *set, int sig) { /* check that sig is in right range */ if (sig <= 0 || sig >= NSIG) { set_errno (EINVAL); syscall_printf ("SIG_ERR = sigdelset signal %d out of range", sig); return -1; } if (*set & SIGTOMASK (sig)) return 1; else return 0; } extern "C" int sigemptyset (sigset_t *set) { *set = (sigset_t) 0; return 0; } extern "C" int sigfillset (sigset_t *set) { *set = ~((sigset_t) 0); return 0; } extern "C" int sigsuspend (const sigset_t *set) { return handle_sigsuspend (*set); } extern "C" int sigpause (int signal_mask) { return handle_sigsuspend ((sigset_t) signal_mask); } extern "C" int pause (void) { return handle_sigsuspend (myself->getsigmask ()); } extern "C" int siginterrupt (int sig, int flag) { struct sigaction act; (void) sigaction(sig, NULL, &act); if (flag) act.sa_flags &= ~SA_RESTART; else act.sa_flags |= SA_RESTART; return sigaction (sig, &act, NULL); } extern "C" int sigwait (const sigset_t *set, int *sig_ptr) { int sig = sigwaitinfo (set, NULL); if (sig > 0) *sig_ptr = sig; return sig > 0 ? 0 : -1; } extern "C" int sigwaitinfo (const sigset_t *set, siginfo_t *info) { pthread_testcancel (); HANDLE h; h = _my_tls.event = CreateEvent (&sec_none_nih, FALSE, FALSE, NULL); if (!h) { __seterrno (); return -1; } _my_tls.sigwait_mask = *set; sig_dispatch_pending (true); int res; switch (WaitForSingleObject (h, INFINITE)) { case WAIT_OBJECT_0: if (!sigismember (set, _my_tls.infodata.si_signo)) { set_errno (EINTR); res = -1; } else { if (info) *info = _my_tls.infodata; res = _my_tls.infodata.si_signo; InterlockedExchange ((LONG *) &_my_tls.sig, (LONG) 0); } break; default: __seterrno (); res = -1; } CloseHandle (h); sigproc_printf ("returning sig %d", res); return res; } extern "C" int sigqueue (pid_t pid, int sig, const union sigval value) { siginfo_t si; pinfo dest (pid); if (!dest) { set_errno (ESRCH); return -1; } si.si_signo = sig; si.si_code = SI_USER; si.si_pid = si.si_uid = si.si_errno = 0; si.si_value = value; return sig_send (dest, si); }