From 34f20eac987b1ca006ebdfe5531a6931243294fb Mon Sep 17 00:00:00 2001
From: "Michael W. Hudson" <mwh@python.net>
Date: Mon, 27 May 2002 15:08:24 +0000
Subject: This is patch

[ 559250 ] more POSIX signal stuff

Adds support (and docs and tests and autoconfery) for posix signal
mask handling -- sigpending, sigprocmask and sigsuspend.
---
 Doc/lib/libsignal.tex       |  54 ++++++++++++--
 Doc/whatsnew/whatsnew23.tex |   6 ++
 Lib/test/test_signal.py     |  68 +++++++++++++++++-
 Misc/NEWS                   |   3 +
 Modules/signalmodule.c      | 171 ++++++++++++++++++++++++++++++++++++++++++++
 configure                   |   7 +-
 configure.in                |   4 +-
 pyconfig.h.in               |   3 +
 8 files changed, 304 insertions(+), 12 deletions(-)

diff --git a/Doc/lib/libsignal.tex b/Doc/lib/libsignal.tex
index e3aa171..3029385 100644
--- a/Doc/lib/libsignal.tex
+++ b/Doc/lib/libsignal.tex
@@ -18,10 +18,6 @@ the handler for \constant{SIGCHLD}, which follows the underlying
 implementation.
 
 \item
-There is no way to ``block'' signals temporarily from critical
-sections (since this is not supported by all \UNIX{} flavors).
-
-\item
 Although Python signal handlers are called asynchronously as far as
 the Python user is concerned, they can only occur between the
 ``atomic'' instructions of the Python interpreter.  This means that
@@ -92,6 +88,16 @@ The variables defined in the \module{signal} module are:
   One more than the number of the highest signal number.
 \end{datadesc}
 
+\begin{datadesc}{SIG_BLOCK}
+\end{datadesc}
+\begin{datadesc}{SIG_UNBLOCK}
+\end{datadesc}
+\begin{datadesc}{SIG_SETMASK}
+  These constants are for use as the first parameter of the
+  \function{sigprocmask} function described below.
+\end{datadesc}
+
+
 The \module{signal} module defines the following functions:
 
 \begin{funcdesc}{alarm}{time}
@@ -144,6 +150,46 @@ The \module{signal} module defines the following functions:
 \obindex{frame}
 \end{funcdesc}
 
+The following functions are supported if your platform does.  Most
+modern \UNIX-alikes now do.
+
+\begin{funcdesc}{sigpending}{}
+  Return the set of pending signals, i.e. a list containing the
+  numbers of those signals that have been raised while blocked.
+  \versionadded{2.3}
+\end{funcdesc}
+
+\begin{funcdesc}{sigprocmask}{how, sigset}
+  Change the list of currently blocked signals.  The parameter
+  \var{how} should be one of \constant{SIG_BLOCK},
+  \constant{SIG_UNBLOCK} or \constant{SIG_SETMASK} and \var{sigset}
+  should be a sequence of signal numbers.  The behaviour of the call
+  depends on the value of \var{how}:
+
+  \begin{tableii}{l|l}{textrm}{Value of \var{how}}{Behaviour of call}
+    \lineii{\constant{SIG_BLOCK}}
+	   {The set of blocked signals is the union of the current set
+            and \var{sigset}.}
+    \lineii{\constant{SIG_UNBLOCK}}      
+           {The signals in \var{sigset} are removed from the current
+            set of blocked signals.  It is legal to attempt to unblock 
+            a signal which is not blocked.}
+    \lineii{\constant{SIG_SETMASK}}
+	   {The set of blocked signals is set to the \var{sigset}.}
+  \end{tableii}
+
+  A list contating the numbers of the previously blocked signals is
+  returned.
+  \versionadded{2.3}
+\end{funcdesc}
+
+\begin{funcdesc}{sigsuspend}{sigset}
+  Temporarily replace the signal mask with \var{sigset} (which should
+  be a sequnce of signal numbers) and suspend the process until a
+  signal is received.
+  \versionadded{2.3}
+\end{funcdesc}
+
 \subsection{Example}
 \nodename{Signal Example}
 
diff --git a/Doc/whatsnew/whatsnew23.tex b/Doc/whatsnew/whatsnew23.tex
index cb5d6fd..489452d 100644
--- a/Doc/whatsnew/whatsnew23.tex
+++ b/Doc/whatsnew/whatsnew23.tex
@@ -532,6 +532,12 @@ contents, and the \code{*=} assignment operator to repeat an array.
 functions: \function{get_history_item()},
 \function{get_current_history_length()}, and \function{redisplay()}.
 
+\item Support for more advanced POSIX signal handling -- specifically
+the functions \function{sigpending}, \function{sigprocmask} and
+\function{sigsupend}, and depending on platform support -- was added
+to the \module{signal} module.  These functions make some previously
+unavoidable race conditions avoidable.
+
 \end{itemize}
 
 
diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py
index 05bdcab..78b90b7 100644
--- a/Lib/test/test_signal.py
+++ b/Lib/test/test_signal.py
@@ -1,8 +1,7 @@
 # Test the signal module
-from test_support import verbose, TestSkipped
+from test_support import verbose, TestSkipped, TestFailed
 import signal
-import os
-import sys
+import os, sys, time
 
 if sys.platform[:3] in ('win', 'os2') or sys.platform=='riscos':
     raise TestSkipped, "Can't test signal on %s" % sys.platform
@@ -64,3 +63,66 @@ try:
 except KeyboardInterrupt:
     if verbose:
         print "KeyboardInterrupt (assume the alarm() went off)"
+
+
+if hasattr(signal, "sigprocmask"):
+    class HupDelivered(Exception):
+        pass
+    def hup(signum, frame):
+        raise HupDelivered
+    def hup2(signum, frame):
+        signal.signal(signal.SIGHUP, hup)
+        return
+    signal.signal(signal.SIGHUP, hup)
+
+    if verbose:
+        print "blocking SIGHUP"
+
+    defaultmask = signal.sigprocmask(signal.SIG_BLOCK, [signal.SIGHUP])
+
+    if verbose:
+        print "sending SIGHUP"
+
+    try:
+        os.kill(pid, signal.SIGHUP)
+    except HupDelivered:
+        raise TestFailed, "HUP not blocked"
+
+    if signal.SIGHUP not in signal.sigpending():
+        raise TestFailed, "HUP not pending"
+
+    if verbose:
+        print "unblocking SIGHUP"
+
+    try:
+        signal.sigprocmask(signal.SIG_UNBLOCK, [signal.SIGHUP])
+    except HupDelivered:
+        pass
+    else:
+        raise TestFailed, "HUP not delivered"
+
+    if verbose:
+        print "testing sigsuspend"
+
+    signal.sigprocmask(signal.SIG_BLOCK, [signal.SIGHUP])
+    signal.signal(signal.SIGHUP, hup2)
+
+    if not os.fork():
+        time.sleep(2)
+        os.kill(pid, signal.SIGHUP)
+        time.sleep(2)
+        os.kill(pid, signal.SIGHUP)
+        os._exit(0)
+    else:
+        try:
+            signal.sigsuspend(defaultmask)
+        except:
+            raise TestFailed, "sigsuspend erroneously raised"
+
+        try:
+            signal.sigsuspend(defaultmask)
+        except HupDelivered:
+            pass
+        else:
+            raise TestFailed, "sigsupsend didn't raise"
+    
diff --git a/Misc/NEWS b/Misc/NEWS
index f544af4..85da0d3 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -106,6 +106,9 @@ Extension modules
   of sizeof(int)!=sizeof(long)!=sizeof(void*) is delayed until dl.open
   is called.
 
+- signal.sigpending, signal.sigprocmask and signal.sigsuspend have
+  been added where available.
+
 Library
 
 - added degree/radian conversion functions to the math module.
diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c
index f6027ab..3cea2dd 100644
--- a/Modules/signalmodule.c
+++ b/Modules/signalmodule.c
@@ -270,6 +270,153 @@ None -- if an unknown handler is in effect\n\
 anything else -- the callable Python object used as a handler\n\
 ";
 
+#ifdef HAVE_SIGPROCMASK /* we assume that having SIGPROCMASK is enough
+			   to guarantee full POSIX signal handling */
+/* returns 0 for success, <0 for failure (with exception set) */
+static int
+_signal_list_to_sigset(PyObject* seq, sigset_t* set, char* mesg)
+{
+	int i, len, val;
+
+	seq = PySequence_Fast(seq, mesg);
+	if (!seq)
+		return -1;
+
+	len = PySequence_Fast_GET_SIZE(seq);
+
+	sigemptyset(set);
+
+	for (i = 0; i < len; i++) {
+		val = PyInt_AsLong(PySequence_Fast_GET_ITEM(seq, i));
+		if (val == -1 && PyErr_Occurred()) {
+			Py_DECREF(seq);
+			return -1;
+		}
+		if (sigaddset(set, val) < 0) {
+			Py_DECREF(seq);
+			PyErr_SetFromErrno(PyExc_ValueError);
+			return -1;
+		}
+	}
+	
+	Py_DECREF(seq);
+	return 0;
+}
+
+static PyObject*
+_signal_sigset_to_list(sigset_t* set)
+{
+	PyObject* ret;
+	PyObject* ob;
+	int i;
+
+	ret = PyList_New(0);
+	if (!ret)
+		return NULL;
+
+	for (i = 1; i < NSIG; i++) {
+		if (sigismember(set, i)) {
+			ob = PyInt_FromLong(i);
+			if (!ob) {
+				Py_DECREF(ret);
+				return NULL;
+			}
+			PyList_Append(ret, ob);
+			Py_DECREF(ob);
+		}
+	}
+
+	return ret;
+}
+
+static PyObject*
+signal_sigprocmask(PyObject* self, PyObject* args)
+{
+	int how;
+	sigset_t newset, oldset;
+	PyObject* seq;
+
+	if (!PyArg_ParseTuple(args, "iO", &how, &seq))
+		return NULL;
+	
+	if (_signal_list_to_sigset(seq, &newset,
+				   "sigprocmask requires a sequence") < 0)
+		return NULL;
+
+	if (sigprocmask(how, &newset, &oldset) < 0) {
+		return PyErr_SetFromErrno(PyExc_ValueError);
+	}
+	
+	if (PyErr_CheckSignals())
+		return NULL;
+
+	return _signal_sigset_to_list(&oldset);
+}
+
+static char sigprocmask_doc[] = 
+"sigprocmask(how, sigset) -> sigset\n\
+\n\
+Change the list of currently blocked signals.  The parameter how should be\n\
+one of SIG_BLOCK, SIG_UNBLOCK or SIG_SETMASK and sigset should be a\n\
+sequence of signal numbers.  The behaviour of the call depends on the value\n\
+of how:\n\
+\n\
+  SIG_BLOCK\n\
+    The set of blocked signals is the union of the current set and the\n\
+    sigset argument.\n\
+  SIG_UNBLOCK\n\
+    The signals in sigset are removed from the current set of blocked\n\
+    signals.  It is legal to attempt to unblock a signal which is not\n\
+    blocked.\n\
+  SIG_SETMASK\n\
+    The set of blocked signals is set to the argument set.\n\
+\n\
+A list contating the numbers of the previously blocked signals is returned.";
+
+static PyObject*
+signal_sigpending(PyObject* self)
+{
+	sigset_t set;
+
+	if (sigpending(&set) < 0) {
+		return PyErr_SetFromErrno(PyExc_ValueError);
+	}
+	
+	return _signal_sigset_to_list(&set);
+}
+
+static char sigpending_doc[] = 
+"sigpending() -> sigset\n\
+\n\
+Return the set of pending signals, i.e. a list containing the numbers of\n\
+those signals that have been raised while blocked.";
+
+static PyObject*
+signal_sigsuspend(PyObject* self, PyObject* arg)
+{
+	sigset_t set;
+
+	if (_signal_list_to_sigset(arg, &set, 
+				   "sigsuspend requires a sequence") < 0)
+		return NULL;
+	
+	Py_BEGIN_ALLOW_THREADS
+	sigsuspend(&set);
+	Py_END_ALLOW_THREADS
+
+	if (PyErr_CheckSignals())
+		return NULL;
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+static char sigsuspend_doc[] = 
+"sigsuspend(sigset) -> None\n\
+\n\
+Temporarily replace the signal mask with sigset (which should be a sequence\n\
+of signal numbers) and suspend the process until a signal is received.";
+#endif
 
 /* List of functions defined in the module */
 static PyMethodDef signal_methods[] = {
@@ -284,6 +431,14 @@ static PyMethodDef signal_methods[] = {
 #endif
 	{"default_int_handler", signal_default_int_handler, 
 	 METH_VARARGS, default_int_handler_doc},
+#ifdef HAVE_SIGPROCMASK
+	{"sigprocmask",         (PyCFunction)signal_sigprocmask, 
+	 METH_VARARGS,          sigprocmask_doc},
+	{"sigpending",          (PyCFunction)signal_sigpending, 
+	 METH_NOARGS,           sigpending_doc},
+	{"sigsuspend",          (PyCFunction)signal_sigsuspend, 
+	 METH_O,                sigsuspend_doc},
+#endif
 	{NULL,			NULL}		/* sentinel */
 };
 
@@ -299,6 +454,10 @@ getsignal() -- get the signal action for a given signal\n\
 pause() -- wait until a signal arrives [Unix only]\n\
 default_int_handler() -- default SIGINT handler\n\
 \n\
+sigpending()  |\n\
+sigprocmask() |-- posix signal mask handling [Unix only]\n\
+sigsuspend()  |\n\
+\n\
 Constants:\n\
 \n\
 SIG_DFL -- used to refer to the system default handler\n\
@@ -547,6 +706,18 @@ initsignal(void)
 	PyDict_SetItemString(d, "SIGINFO", x);
         Py_XDECREF(x);
 #endif
+#ifdef HAVE_SIGPROCMASK
+	x = PyInt_FromLong(SIG_BLOCK);
+	PyDict_SetItemString(d, "SIG_BLOCK", x);
+        Py_XDECREF(x);
+	x = PyInt_FromLong(SIG_UNBLOCK);
+	PyDict_SetItemString(d, "SIG_UNBLOCK", x);
+        Py_XDECREF(x);
+	x = PyInt_FromLong(SIG_SETMASK);
+	PyDict_SetItemString(d, "SIG_SETMASK", x);
+        Py_XDECREF(x);
+#endif
+
         if (!PyErr_Occurred())
                 return;
 
diff --git a/configure b/configure
index 21ca48b..aca0410 100755
--- a/configure
+++ b/configure
@@ -1,5 +1,5 @@
 #! /bin/sh
-# From configure.in Revision: 1.316 .
+# From configure.in Revision: 1.317 .
 # Guess values for system-dependent variables and create Makefiles.
 # Generated by GNU Autoconf 2.53.
 #
@@ -11278,6 +11278,7 @@ echo "${ECHO_T}MACHDEP_OBJS" >&6
 
 
 
+
 for ac_func in alarm chown chroot clock confstr ctermid ctermid_r execv \
  fchdir flock fork fsync fdatasync fpathconf ftime ftruncate \
  gai_strerror getgroups getlogin getpeername getpid getpwent getwd \
@@ -11286,8 +11287,8 @@ for ac_func in alarm chown chroot clock confstr ctermid ctermid_r execv \
  putenv readlink \
  select setegid seteuid setgid setgroups \
  setlocale setregid setreuid setsid setpgid setuid setvbuf snprintf \
- sigaction siginterrupt sigrelse strftime strptime symlink sysconf \
- tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
+ sigaction siginterrupt sigprocmask sigrelse strftime strptime symlink \
+ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
  truncate uname unsetenv waitpid _getpty getpriority
 do
 as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh`
diff --git a/configure.in b/configure.in
index dcae3d8..37c6f63 100644
--- a/configure.in
+++ b/configure.in
@@ -1593,8 +1593,8 @@ AC_CHECK_FUNCS(alarm chown chroot clock confstr ctermid ctermid_r execv \
  putenv readlink \
  select setegid seteuid setgid setgroups \
  setlocale setregid setreuid setsid setpgid setuid setvbuf snprintf \
- sigaction siginterrupt sigrelse strftime strptime symlink sysconf \
- tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
+ sigaction siginterrupt sigprocmask sigrelse strftime strptime symlink \
+ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
  truncate uname unsetenv waitpid _getpty getpriority)
 
 # check for openpty and forkpty
diff --git a/pyconfig.h.in b/pyconfig.h.in
index 8e96db2..c8fa5af 100644
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -383,6 +383,9 @@
 /* Define to 1 if you have the <signal.h> header file. */
 #undef HAVE_SIGNAL_H
 
+/* Define to 1 if you have the `sigprocmask' function. */
+#undef HAVE_SIGPROCMASK
+
 /* Define to 1 if you have the `sigrelse' function. */
 #undef HAVE_SIGRELSE
 
-- 
cgit v0.12

