Skip to content

Commit 42bcc61

Browse files
committed
Adjust pcntl_sigprocmask()
1 parent 12d9b40 commit 42bcc61

File tree

4 files changed

+158
-86
lines changed

4 files changed

+158
-86
lines changed

ext/pcntl/pcntl.c

Lines changed: 85 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -710,53 +710,116 @@ PHP_FUNCTION(pcntl_signal_dispatch)
710710
}
711711
/* }}} */
712712

713+
/* Common helper function for these 3 wrapper functions */
714+
#if defined(HAVE_SIGWAITINFO) || defined(HAVE_SIGTIMEDWAIT) || defined(HAVE_SIGPROCMASK)
715+
static bool php_pcntl_set_user_signal_infos(
716+
/* const */ HashTable *const user_signals,
717+
sigset_t *const set,
718+
size_t arg_num
719+
) {
720+
if (zend_hash_num_elements(user_signals) == 0) {
721+
zend_argument_value_error(arg_num, "cannot be empty");
722+
return false;
723+
}
724+
725+
errno = 0;
726+
if (sigemptyset(set) != 0) {
727+
PCNTL_G(last_error) = errno;
728+
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
729+
return false;
730+
}
731+
732+
zval *user_signal_no;
733+
ZEND_HASH_FOREACH_VAL(user_signals, user_signal_no) {
734+
bool failed = true;
735+
zend_long tmp = zval_try_get_long(user_signal_no, &failed);
736+
737+
if (failed) {
738+
zend_argument_type_error(arg_num, "signals must be of type int, %s given", zend_zval_value_name(user_signal_no));
739+
return false;
740+
}
741+
/* Signals are positive integers */
742+
if (tmp < 1 || tmp >= PCNTL_G(num_signals)) {
743+
/* PCNTL_G(num_signals) stores +1 from the last valid signal */
744+
zend_argument_value_error(arg_num, "signals must be between 1 and %d", PCNTL_G(num_signals)-1);
745+
return false;
746+
}
747+
748+
int signal_no = (int) tmp;
749+
errno = 0;
750+
if (sigaddset(set, signal_no) != 0) {
751+
PCNTL_G(last_error) = errno;
752+
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
753+
return false;
754+
}
755+
} ZEND_HASH_FOREACH_END();
756+
return true;
757+
}
758+
#endif
759+
713760
#ifdef HAVE_SIGPROCMASK
714761
/* {{{ Examine and change blocked signals */
715762
PHP_FUNCTION(pcntl_sigprocmask)
716763
{
717-
zend_long how, signo;
718-
zval *user_set, *user_oldset = NULL, *user_signo;
719-
sigset_t set, oldset;
764+
zend_long how;
765+
HashTable *user_set;
766+
/* Optional by-ref out-param array of old signals */
767+
zval *user_old_set = NULL;
720768

721769
ZEND_PARSE_PARAMETERS_START(2, 3)
722770
Z_PARAM_LONG(how)
723-
Z_PARAM_ARRAY(user_set)
771+
Z_PARAM_ARRAY_HT(user_set)
724772
Z_PARAM_OPTIONAL
725-
Z_PARAM_ZVAL(user_oldset)
773+
Z_PARAM_ZVAL(user_old_set)
726774
ZEND_PARSE_PARAMETERS_END();
727775

728-
if (sigemptyset(&set) != 0 || sigemptyset(&oldset) != 0) {
776+
if (how != SIG_BLOCK && how != SIG_UNBLOCK && how != SIG_SETMASK) {
777+
zend_argument_value_error(1, "must be one of SIG_BLOCK, SIG_UNBLOCK, or SIG_SETMASK");
778+
RETURN_THROWS();
779+
}
780+
781+
errno = 0;
782+
sigset_t old_set;
783+
if (sigemptyset(&old_set) != 0) {
729784
PCNTL_G(last_error) = errno;
730785
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
731786
RETURN_FALSE;
732787
}
733788

734-
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(user_set), user_signo) {
735-
signo = zval_get_long(user_signo);
736-
if (sigaddset(&set, signo) != 0) {
789+
sigset_t set;
790+
/* Can set an empty array of signals for SIG_SETMASK */
791+
if (how == SIG_SETMASK && zend_hash_num_elements(user_set) == 0) {
792+
if (sigemptyset(&set) != 0) {
737793
PCNTL_G(last_error) = errno;
738794
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
739795
RETURN_FALSE;
740796
}
741-
} ZEND_HASH_FOREACH_END();
797+
goto skip_setting_user_signals;
798+
}
799+
bool status = php_pcntl_set_user_signal_infos(user_set, &set, 2);
800+
/* Some error occurred */
801+
if (!status) {
802+
RETURN_FALSE;
803+
}
804+
skip_setting_user_signals:
742805

743-
if (sigprocmask(how, &set, &oldset) != 0) {
806+
if (sigprocmask(how, &set, &old_set) != 0) {
744807
PCNTL_G(last_error) = errno;
745808
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
746809
RETURN_FALSE;
747810
}
748811

749-
if (user_oldset != NULL) {
750-
user_oldset = zend_try_array_init(user_oldset);
751-
if (!user_oldset) {
812+
if (user_old_set != NULL) {
813+
user_old_set = zend_try_array_init(user_old_set);
814+
if (!user_old_set) {
752815
RETURN_THROWS();
753816
}
754817

755-
for (signo = 1; signo < PCNTL_G(num_signals); ++signo) {
756-
if (sigismember(&oldset, signo) != 1) {
818+
for (int signal_no = 1; signal_no < PCNTL_G(num_signals); ++signal_no) {
819+
if (sigismember(&old_set, signal_no) != 1) {
757820
continue;
758821
}
759-
add_next_index_long(user_oldset, signo);
822+
add_next_index_long(user_old_set, signal_no);
760823
}
761824
}
762825

@@ -766,50 +829,7 @@ PHP_FUNCTION(pcntl_sigprocmask)
766829
#endif
767830

768831
#ifdef HAVE_STRUCT_SIGINFO_T
769-
# if defined(HAVE_SIGWAITINFO) && defined(HAVE_SIGTIMEDWAIT)
770-
771-
static bool php_pcntl_process_user_signal_infos(
772-
/* const */ HashTable *const user_signals,
773-
sigset_t *const set
774-
) {
775-
if (zend_hash_num_elements(user_signals) == 0) {
776-
zend_argument_value_error(1, "cannot be empty");
777-
return false;
778-
}
779-
780-
errno = 0;
781-
if (sigemptyset(set) != 0) {
782-
PCNTL_G(last_error) = errno;
783-
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
784-
return false;
785-
}
786-
787-
zval *user_signal_no;
788-
ZEND_HASH_FOREACH_VAL(user_signals, user_signal_no) {
789-
bool failed = true;
790-
zend_long tmp = zval_try_get_long(user_signal_no, &failed);
791-
792-
if (failed) {
793-
zend_argument_type_error(1, "signals must be of type int, %s given", zend_zval_value_name(user_signal_no));
794-
return false;
795-
}
796-
/* Signals are positive integers */
797-
if (tmp < 1 || tmp >= PCNTL_G(num_signals)) {
798-
/* PCNTL_G(num_signals) stores +1 from the last valid signal */
799-
zend_argument_value_error(1, "signals must be between 1 and %d", PCNTL_G(num_signals)-1);
800-
return false;
801-
}
802-
803-
int signal_no = (int) tmp;
804-
errno = 0;
805-
if (sigaddset(set, signal_no) != 0) {
806-
PCNTL_G(last_error) = errno;
807-
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
808-
return false;
809-
}
810-
} ZEND_HASH_FOREACH_END();
811-
return true;
812-
}
832+
# ifdef HAVE_SIGWAITINFO
813833

814834
/* {{{ Synchronously wait for queued signals */
815835
PHP_FUNCTION(pcntl_sigwaitinfo)
@@ -825,7 +845,7 @@ PHP_FUNCTION(pcntl_sigwaitinfo)
825845
ZEND_PARSE_PARAMETERS_END();
826846

827847
sigset_t set;
828-
bool status = php_pcntl_process_user_signal_infos(user_set, &set);
848+
bool status = php_pcntl_set_user_signal_infos(user_set, &set, 1);
829849
/* Some error occurred */
830850
if (!status) {
831851
RETURN_FALSE;
@@ -851,7 +871,8 @@ PHP_FUNCTION(pcntl_sigwaitinfo)
851871
RETURN_LONG(signal_no);
852872
}
853873
/* }}} */
854-
874+
# endif
875+
# ifdef HAVE_SIGTIMEDWAIT
855876
/* {{{ Wait for queued signals */
856877
PHP_FUNCTION(pcntl_sigtimedwait)
857878
{
@@ -870,7 +891,7 @@ PHP_FUNCTION(pcntl_sigtimedwait)
870891
ZEND_PARSE_PARAMETERS_END();
871892

872893
sigset_t set;
873-
bool status = php_pcntl_process_user_signal_infos(user_set, &set);
894+
bool status = php_pcntl_set_user_signal_infos(user_set, &set, 1);
874895
/* Some error occurred */
875896
if (!status) {
876897
RETURN_FALSE;

ext/pcntl/tests/002.phpt

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ pcntl
55
posix
66
--SKIPIF--
77
<?php
8-
if (!function_exists('pcntl_sigwaitinfo') or !function_exists('pcntl_sigtimedwait')) die('skip required functionality is not available');
8+
if (
9+
!function_exists('pcntl_sigprocmask')
10+
or !function_exists('pcntl_sigwaitinfo')
11+
or !function_exists('pcntl_sigtimedwait')
12+
) { die('skip required functionality is not available'); }
913
elseif (!defined('CLD_EXITED')) die('skip CLD_EXITED not defined');
1014
elseif (getenv('SKIP_ASAN')) die('skip Fails intermittently under asan/msan');
1115
elseif (getenv("SKIP_REPEAT")) die("skip cannot be repeated");
@@ -49,21 +53,6 @@ if ($pid == -1) {
4953
echo "signo === pid\n";
5054
var_dump($siginfo['pid'] === $pid);
5155
pcntl_waitpid($pid, $status);
52-
53-
set_error_handler(function($errno, $errstr) { echo "Error triggered\n"; }, E_WARNING);
54-
55-
echo "sigprocmask with invalid arguments\n";
56-
57-
/* Valgrind expectedly complains about this:
58-
* "sigprocmask: unknown 'how' field 2147483647"
59-
* Skip */
60-
if (getenv("USE_ZEND_ALLOC") !== '0') {
61-
var_dump(pcntl_sigprocmask(PHP_INT_MAX, array(SIGTERM)));
62-
} else {
63-
echo "Error triggered\n";
64-
echo "bool(false)\n";
65-
}
66-
var_dump(pcntl_sigprocmask(SIG_SETMASK, array(0)));
6756
} else {
6857
$siginfo = NULL;
6958
pcntl_sigtimedwait(array(SIGINT), $siginfo, 3600, 0);
@@ -88,8 +77,3 @@ signo === uid
8877
bool(true)
8978
signo === pid
9079
bool(true)
91-
sigprocmask with invalid arguments
92-
Error triggered
93-
bool(false)
94-
Error triggered
95-
bool(false)

ext/pcntl/tests/003.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pcntl
55
posix
66
--SKIPIF--
77
<?php
8-
if (!function_exists('pcntl_sigwaitinfo') or !function_exists('pcntl_sigtimedwait')) die('skip required functionality is not available');
8+
if (!function_exists("pcntl_sigprocmask")) die("skip pcntl_sigprocmask() not available");
99
?>
1010
--FILE--
1111
<?php
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
--TEST--
2+
pcntl_sigprocmask() errors
3+
--EXTENSIONS--
4+
pcntl
5+
--SKIPIF--
6+
<?php if (!function_exists("pcntl_sigprocmask")) die("skip pcntl_sigprocmask() not available"); ?>
7+
--INI--
8+
max_execution_time=0
9+
--FILE--
10+
<?php
11+
12+
/* Invalid mode */
13+
try {
14+
$signals = [SIGTERM];
15+
$signal_no = pcntl_sigprocmask(-1, $signals);
16+
var_dump($signal_no);
17+
} catch (\Throwable $e) {
18+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
19+
}
20+
try {
21+
/* This used to return -1 prior to PHP 8.3.0 */
22+
$signals = [];
23+
$signal_no = pcntl_sigprocmask(SIG_BLOCK, $signals);
24+
var_dump($signal_no);
25+
} catch (\Throwable $e) {
26+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
27+
}
28+
29+
try {
30+
$signals = [0];
31+
$signal_no = pcntl_sigprocmask(SIG_BLOCK, $signals);
32+
var_dump($signal_no);
33+
} catch (\Throwable $e) {
34+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
35+
}
36+
try {
37+
$signals = [-1];
38+
$signal_no = pcntl_sigprocmask(SIG_BLOCK, $signals);
39+
var_dump($signal_no);
40+
} catch (\Throwable $e) {
41+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
42+
}
43+
44+
try {
45+
$signals = ["not a signal"];
46+
$signal_no = pcntl_sigprocmask(SIG_BLOCK, $signals);
47+
var_dump($signal_no);
48+
} catch (\Throwable $e) {
49+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
50+
}
51+
52+
/* Unlikely valid signal */
53+
try {
54+
$signals = [2**10];
55+
$signal_no = pcntl_sigprocmask(SIG_BLOCK, $signals);
56+
var_dump($signal_no);
57+
} catch (\Throwable $e) {
58+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
59+
}
60+
?>
61+
--EXPECTF--
62+
ValueError: pcntl_sigprocmask(): Argument #1 ($mode) must be one of SIG_BLOCK, SIG_UNBLOCK, or SIG_SETMASK
63+
ValueError: pcntl_sigprocmask(): Argument #2 ($signals) cannot be empty
64+
ValueError: pcntl_sigprocmask(): Argument #2 ($signals) signals must be between 1 and %d
65+
ValueError: pcntl_sigprocmask(): Argument #2 ($signals) signals must be between 1 and %d
66+
TypeError: pcntl_sigprocmask(): Argument #2 ($signals) signals must be of type int, string given
67+
ValueError: pcntl_sigprocmask(): Argument #2 ($signals) signals must be between 1 and %d

0 commit comments

Comments
 (0)