Skip to content

Commit 20cc695

Browse files
authored
gh-64783: Fix signal.NSIG value on FreeBSD (#91929)
Fix signal.NSIG value on FreeBSD to accept signal numbers greater than 32, like signal.SIGRTMIN and signal.SIGRTMAX. * Add Py_NSIG constant. * Add pycore_signal.h internal header file. * _Py_Sigset_Converter() now includes the range of valid signals in the error message.
1 parent 61381d7 commit 20cc695

11 files changed

+81
-51
lines changed

Doc/library/signal.rst

+1
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ The variables defined in the :mod:`signal` module are:
266266
.. data:: NSIG
267267

268268
One more than the number of the highest signal number.
269+
Use :func:`valid_signals` to get valid signal numbers.
269270

270271

271272
.. data:: ITIMER_REAL

Include/internal/pycore_pylifecycle.h

-16
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,8 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11-
#ifdef HAVE_SIGNAL_H
12-
#include <signal.h>
13-
#endif
14-
1511
#include "pycore_runtime.h" // _PyRuntimeState
1612

17-
#ifndef NSIG
18-
# if defined(_NSIG)
19-
# define NSIG _NSIG /* For BSD/SysV */
20-
# elif defined(_SIGMAX)
21-
# define NSIG (_SIGMAX + 1) /* For QNX */
22-
# elif defined(SIGMAX)
23-
# define NSIG (SIGMAX + 1) /* For djgpp */
24-
# else
25-
# define NSIG 64 /* Use a reasonable default value */
26-
# endif
27-
#endif
28-
2913
/* Forward declarations */
3014
struct _PyArgv;
3115
struct pyruntimestate;

Include/internal/pycore_signal.h

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Define Py_NSIG constant for signal handling.
2+
3+
#ifndef Py_INTERNAL_SIGNAL_H
4+
#define Py_INTERNAL_SIGNAL_H
5+
#ifdef __cplusplus
6+
extern "C" {
7+
#endif
8+
9+
#ifndef Py_BUILD_CORE
10+
# error "this header requires Py_BUILD_CORE define"
11+
#endif
12+
13+
#include <signal.h> // NSIG
14+
15+
#ifdef _SIG_MAXSIG
16+
// gh-91145: On FreeBSD, <signal.h> defines NSIG as 32: it doesn't include
17+
// realtime signals: [SIGRTMIN,SIGRTMAX]. Use _SIG_MAXSIG instead. For
18+
// example on x86-64 FreeBSD 13, SIGRTMAX is 126 and _SIG_MAXSIG is 128.
19+
# define Py_NSIG _SIG_MAXSIG
20+
#elif defined(NSIG)
21+
# define Py_NSIG NSIG
22+
#elif defined(_NSIG)
23+
# define Py_NSIG _NSIG // BSD/SysV
24+
#elif defined(_SIGMAX)
25+
# define Py_NSIG (_SIGMAX + 1) // QNX
26+
#elif defined(SIGMAX)
27+
# define Py_NSIG (SIGMAX + 1) // djgpp
28+
#else
29+
# define Py_NSIG 64 // Use a reasonable default value
30+
#endif
31+
32+
#ifdef __cplusplus
33+
}
34+
#endif
35+
#endif // !Py_INTERNAL_SIGNAL_H

Lib/test/test_signal.py

+10
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,16 @@ def test_valid_signals(self):
116116
self.assertNotIn(signal.NSIG, s)
117117
self.assertLess(len(s), signal.NSIG)
118118

119+
# gh-91145: Make sure that all SIGxxx constants exposed by the Python
120+
# signal module have a number in the [0; signal.NSIG-1] range.
121+
for name in dir(signal):
122+
if not name.startswith("SIG"):
123+
continue
124+
with self.subTest(name=name):
125+
signum = getattr(signal, name)
126+
self.assertGreaterEqual(signum, 0)
127+
self.assertLess(signum, signal.NSIG)
128+
119129
@unittest.skipUnless(sys.executable, "sys.executable required.")
120130
@support.requires_subprocess()
121131
def test_keyboard_interrupt_exit_code(self):

Makefile.pre.in

+1
Original file line numberDiff line numberDiff line change
@@ -1622,6 +1622,7 @@ PYTHON_HEADERS= \
16221622
$(srcdir)/Include/internal/pycore_pystate.h \
16231623
$(srcdir)/Include/internal/pycore_runtime.h \
16241624
$(srcdir)/Include/internal/pycore_runtime_init.h \
1625+
$(srcdir)/Include/internal/pycore_signal.h \
16251626
$(srcdir)/Include/internal/pycore_sliceobject.h \
16261627
$(srcdir)/Include/internal/pycore_strhex.h \
16271628
$(srcdir)/Include/internal/pycore_structseq.h \
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :data:`signal.NSIG` value on FreeBSD to accept signal numbers greater than
2+
32, like :data:`signal.SIGRTMIN` and :data:`signal.SIGRTMAX`. Patch by Victor
3+
Stinner.

Modules/faulthandler.c

+5-17
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "pycore_initconfig.h" // _PyStatus_ERR
33
#include "pycore_pyerrors.h" // _Py_DumpExtensionModules
44
#include "pycore_pystate.h" // _PyThreadState_GET()
5+
#include "pycore_signal.h" // Py_NSIG
56
#include "pycore_traceback.h" // _Py_DumpTracebackThreads
67

78
#include "frameobject.h"
@@ -115,19 +116,6 @@ typedef struct {
115116

116117
static user_signal_t *user_signals;
117118

118-
/* the following macros come from Python: Modules/signalmodule.c */
119-
#ifndef NSIG
120-
# if defined(_NSIG)
121-
# define NSIG _NSIG /* For BSD/SysV */
122-
# elif defined(_SIGMAX)
123-
# define NSIG (_SIGMAX + 1) /* For QNX */
124-
# elif defined(SIGMAX)
125-
# define NSIG (SIGMAX + 1) /* For djgpp */
126-
# else
127-
# define NSIG 64 /* Use a reasonable default value */
128-
# endif
129-
#endif
130-
131119
static void faulthandler_user(int signum);
132120
#endif /* FAULTHANDLER_USER */
133121

@@ -896,7 +884,7 @@ check_signum(int signum)
896884
return 0;
897885
}
898886
}
899-
if (signum < 1 || NSIG <= signum) {
887+
if (signum < 1 || Py_NSIG <= signum) {
900888
PyErr_SetString(PyExc_ValueError, "signal number out of range");
901889
return 0;
902890
}
@@ -935,7 +923,7 @@ faulthandler_register_py(PyObject *self,
935923
return NULL;
936924

937925
if (user_signals == NULL) {
938-
user_signals = PyMem_Calloc(NSIG, sizeof(user_signal_t));
926+
user_signals = PyMem_Calloc(Py_NSIG, sizeof(user_signal_t));
939927
if (user_signals == NULL)
940928
return PyErr_NoMemory();
941929
}
@@ -1215,7 +1203,7 @@ faulthandler_traverse(PyObject *module, visitproc visit, void *arg)
12151203
Py_VISIT(thread.file);
12161204
#ifdef FAULTHANDLER_USER
12171205
if (user_signals != NULL) {
1218-
for (size_t signum=0; signum < NSIG; signum++)
1206+
for (size_t signum=0; signum < Py_NSIG; signum++)
12191207
Py_VISIT(user_signals[signum].file);
12201208
}
12211209
#endif
@@ -1416,7 +1404,7 @@ void _PyFaulthandler_Fini(void)
14161404
#ifdef FAULTHANDLER_USER
14171405
/* user */
14181406
if (user_signals != NULL) {
1419-
for (size_t signum=0; signum < NSIG; signum++) {
1407+
for (size_t signum=0; signum < Py_NSIG; signum++) {
14201408
faulthandler_unregister(&user_signals[signum], signum);
14211409
}
14221410
PyMem_Free(user_signals);

Modules/posixmodule.c

+4-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "pycore_moduleobject.h" // _PyModule_GetState()
3030
#include "pycore_object.h" // _PyObject_LookupSpecial()
3131
#include "pycore_pystate.h" // _PyInterpreterState_GET()
32+
#include "pycore_signal.h" // Py_NSIG
3233

3334
#include "structmember.h" // PyMemberDef
3435
#ifndef MS_WINDOWS
@@ -1503,10 +1504,11 @@ _Py_Sigset_Converter(PyObject *obj, void *addr)
15031504
while ((item = PyIter_Next(iterator)) != NULL) {
15041505
signum = PyLong_AsLongAndOverflow(item, &overflow);
15051506
Py_DECREF(item);
1506-
if (signum <= 0 || signum >= NSIG) {
1507+
if (signum <= 0 || signum >= Py_NSIG) {
15071508
if (overflow || signum != -1 || !PyErr_Occurred()) {
15081509
PyErr_Format(PyExc_ValueError,
1509-
"signal number %ld out of range", signum);
1510+
"signal number %ld out of range [1; %i]",
1511+
signum, Py_NSIG - 1);
15101512
}
15111513
goto error;
15121514
}

Modules/signalmodule.c

+18-16
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
#include "pycore_atomic.h" // _Py_atomic_int
88
#include "pycore_call.h" // _PyObject_Call()
99
#include "pycore_ceval.h" // _PyEval_SignalReceived()
10+
#include "pycore_emscripten_signal.h" // _Py_CHECK_EMSCRIPTEN_SIGNALS
1011
#include "pycore_fileutils.h" // _Py_BEGIN_SUPPRESS_IPH
1112
#include "pycore_frame.h" // _PyInterpreterFrame
1213
#include "pycore_moduleobject.h" // _PyModule_GetState()
1314
#include "pycore_pyerrors.h" // _PyErr_SetString()
14-
#include "pycore_pylifecycle.h" // NSIG
1515
#include "pycore_pystate.h" // _PyThreadState_GET()
16-
#include "pycore_emscripten_signal.h" // _Py_CHECK_EMSCRIPTEN_SIGNALS
16+
#include "pycore_signal.h" // Py_NSIG
1717

1818
#ifndef MS_WINDOWS
1919
# include "posixmodule.h"
@@ -106,7 +106,7 @@ static volatile struct {
106106
* (even though it would probably be otherwise, anyway).
107107
*/
108108
_Py_atomic_address func;
109-
} Handlers[NSIG];
109+
} Handlers[Py_NSIG];
110110

111111
#ifdef MS_WINDOWS
112112
#define INVALID_FD ((SOCKET_T)-1)
@@ -542,7 +542,7 @@ signal_signal_impl(PyObject *module, int signalnum, PyObject *handler)
542542
"of the main interpreter");
543543
return NULL;
544544
}
545-
if (signalnum < 1 || signalnum >= NSIG) {
545+
if (signalnum < 1 || signalnum >= Py_NSIG) {
546546
_PyErr_SetString(tstate, PyExc_ValueError,
547547
"signal number out of range");
548548
return NULL;
@@ -601,7 +601,7 @@ signal_getsignal_impl(PyObject *module, int signalnum)
601601
/*[clinic end generated code: output=35b3e0e796fd555e input=ac23a00f19dfa509]*/
602602
{
603603
PyObject *old_handler;
604-
if (signalnum < 1 || signalnum >= NSIG) {
604+
if (signalnum < 1 || signalnum >= Py_NSIG) {
605605
PyErr_SetString(PyExc_ValueError,
606606
"signal number out of range");
607607
return NULL;
@@ -634,7 +634,7 @@ signal_strsignal_impl(PyObject *module, int signalnum)
634634
{
635635
const char *res;
636636

637-
if (signalnum < 1 || signalnum >= NSIG) {
637+
if (signalnum < 1 || signalnum >= Py_NSIG) {
638638
PyErr_SetString(PyExc_ValueError,
639639
"signal number out of range");
640640
return NULL;
@@ -712,7 +712,7 @@ static PyObject *
712712
signal_siginterrupt_impl(PyObject *module, int signalnum, int flag)
713713
/*[clinic end generated code: output=063816243d85dd19 input=4160acacca3e2099]*/
714714
{
715-
if (signalnum < 1 || signalnum >= NSIG) {
715+
if (signalnum < 1 || signalnum >= Py_NSIG) {
716716
PyErr_SetString(PyExc_ValueError,
717717
"signal number out of range");
718718
return NULL;
@@ -964,7 +964,7 @@ sigset_to_set(sigset_t mask)
964964
if (result == NULL)
965965
return NULL;
966966

967-
for (sig = 1; sig < NSIG; sig++) {
967+
for (sig = 1; sig < Py_NSIG; sig++) {
968968
if (sigismember(&mask, sig) != 1)
969969
continue;
970970

@@ -1439,13 +1439,15 @@ the first is the signal number, the second is the interrupted stack frame.");
14391439
static int
14401440
signal_add_constants(PyObject *module)
14411441
{
1442+
if (PyModule_AddIntConstant(module, "NSIG", Py_NSIG) < 0) {
1443+
return -1;
1444+
}
1445+
14421446
#define ADD_INT_MACRO(macro) \
14431447
if (PyModule_AddIntConstant(module, #macro, macro) < 0) { \
14441448
return -1; \
14451449
}
14461450

1447-
ADD_INT_MACRO(NSIG);
1448-
14491451
// SIG_xxx pthread_sigmask() constants
14501452
#ifdef SIG_BLOCK
14511453
ADD_INT_MACRO(SIG_BLOCK);
@@ -1605,7 +1607,7 @@ static int
16051607
signal_get_set_handlers(signal_state_t *state, PyObject *mod_dict)
16061608
{
16071609
// Get signal handlers
1608-
for (int signum = 1; signum < NSIG; signum++) {
1610+
for (int signum = 1; signum < Py_NSIG; signum++) {
16091611
void (*c_handler)(int) = PyOS_getsig(signum);
16101612
PyObject *func;
16111613
if (c_handler == SIG_DFL) {
@@ -1762,7 +1764,7 @@ _PySignal_Fini(void)
17621764
signal_state_t *state = &signal_global_state;
17631765

17641766
// Restore default signals and clear handlers
1765-
for (int signum = 1; signum < NSIG; signum++) {
1767+
for (int signum = 1; signum < Py_NSIG; signum++) {
17661768
PyObject *func = get_handler(signum);
17671769
_Py_atomic_store_relaxed(&Handlers[signum].tripped, 0);
17681770
set_handler(signum, NULL);
@@ -1828,7 +1830,7 @@ _PyErr_CheckSignalsTstate(PyThreadState *tstate)
18281830

18291831
_PyInterpreterFrame *frame = tstate->cframe->current_frame;
18301832
signal_state_t *state = &signal_global_state;
1831-
for (int i = 1; i < NSIG; i++) {
1833+
for (int i = 1; i < Py_NSIG; i++) {
18321834
if (!_Py_atomic_load_relaxed(&Handlers[i].tripped)) {
18331835
continue;
18341836
}
@@ -1905,7 +1907,7 @@ _PyErr_CheckSignals(void)
19051907
int
19061908
PyErr_SetInterruptEx(int signum)
19071909
{
1908-
if (signum < 1 || signum >= NSIG) {
1910+
if (signum < 1 || signum >= Py_NSIG) {
19091911
return -1;
19101912
}
19111913

@@ -1995,7 +1997,7 @@ _PySignal_Init(int install_signal_handlers)
19951997
}
19961998
#endif
19971999

1998-
for (int signum = 1; signum < NSIG; signum++) {
2000+
for (int signum = 1; signum < Py_NSIG; signum++) {
19992001
_Py_atomic_store_relaxed(&Handlers[signum].tripped, 0);
20002002
}
20012003

@@ -2045,7 +2047,7 @@ _clear_pending_signals(void)
20452047
}
20462048

20472049
_Py_atomic_store(&is_tripped, 0);
2048-
for (int i = 1; i < NSIG; ++i) {
2050+
for (int i = 1; i < Py_NSIG; ++i) {
20492051
_Py_atomic_store_relaxed(&Handlers[i].tripped, 0);
20502052
}
20512053
}

PCbuild/pythoncore.vcxproj

+1
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@
238238
<ClInclude Include="..\Include\internal\pycore_pystate.h" />
239239
<ClInclude Include="..\Include\internal\pycore_runtime.h" />
240240
<ClInclude Include="..\Include\internal\pycore_runtime_init.h" />
241+
<ClInclude Include="..\Include\internal\pycore_signal.h" />
241242
<ClInclude Include="..\Include\internal\pycore_sliceobject.h" />
242243
<ClInclude Include="..\Include\internal\pycore_strhex.h" />
243244
<ClInclude Include="..\Include\internal\pycore_structseq.h" />

PCbuild/pythoncore.vcxproj.filters

+3
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,9 @@
618618
<ClInclude Include="..\Include\internal\pycore_runtime_init.h">
619619
<Filter>Include\internal</Filter>
620620
</ClInclude>
621+
<ClInclude Include="..\Include\internal\pycore_signal.h">
622+
<Filter>Include\internal</Filter>
623+
</ClInclude>
621624
<ClInclude Include="..\Include\internal\pycore_sliceobject.h">
622625
<Filter>Include\internal</Filter>
623626
</ClInclude>

0 commit comments

Comments
 (0)