Skip to content

Commit 1bdb0fd

Browse files
authored
Refactor pcntl_sigprocmask()/pcntl_sigwaitinfo()/pcntl_sigtimedwait() (#11860)
1 parent d882c5d commit 1bdb0fd

File tree

8 files changed

+460
-136
lines changed

8 files changed

+460
-136
lines changed

UPGRADING

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ PHP 8.4 UPGRADE NOTES
3030
. The DSN's credentials, when set, are given priority over their PDO
3131
constructor counterparts, being closer to the documentation states.
3232

33+
- PCNTL:
34+
. The functions pcntl_sigprocmask(), pcntl_sigwaitinfo() and
35+
pcntl_sigtimedwait() now throw:
36+
- A ValueError if the $signals array is empty (except for
37+
pcntl_sigprocmask() if the $mode is SIG_SETMASK).
38+
- A TypeError if a value of the $signals array is not an integer
39+
- A ValueError if a value of the $signals array is not a valid signal number
40+
Moreover, those functions now always return false on failure.
41+
In some case previously it could return the value -1.
42+
. The function pcntl_sigprocmask() will also now throw:
43+
- A ValueError if $mode is not one of SIG_BLOCK, SIG_UNBLOCK, or SIG_SETMASK
44+
. The function pcntl_sigtimedwait() will also now throw:
45+
- A ValueError if $seconds is less than 0
46+
- A ValueError if $nanoseconds is less than 0 or greater than 1e9
47+
- A ValueError if both $seconds and $nanoseconds are 0
48+
3349
- SimpleXML:
3450
. Get methods called, or casting to a string on a SimpleXMLElement will no
3551
longer implicitly reset the iterator data, unless explicitly rewound.

ext/pcntl/pcntl.c

Lines changed: 162 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -705,53 +705,107 @@ PHP_FUNCTION(pcntl_signal_dispatch)
705705
}
706706
/* }}} */
707707

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

716765
ZEND_PARSE_PARAMETERS_START(2, 3)
717766
Z_PARAM_LONG(how)
718-
Z_PARAM_ARRAY(user_set)
767+
Z_PARAM_ARRAY_HT(user_set)
719768
Z_PARAM_OPTIONAL
720-
Z_PARAM_ZVAL(user_oldset)
769+
Z_PARAM_ZVAL(user_old_set)
721770
ZEND_PARSE_PARAMETERS_END();
722771

723-
if (sigemptyset(&set) != 0 || sigemptyset(&oldset) != 0) {
772+
if (how != SIG_BLOCK && how != SIG_UNBLOCK && how != SIG_SETMASK) {
773+
zend_argument_value_error(1, "must be one of SIG_BLOCK, SIG_UNBLOCK, or SIG_SETMASK");
774+
RETURN_THROWS();
775+
}
776+
777+
errno = 0;
778+
sigset_t old_set;
779+
if (sigemptyset(&old_set) != 0) {
724780
PCNTL_G(last_error) = errno;
725781
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
726782
RETURN_FALSE;
727783
}
728784

729-
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(user_set), user_signo) {
730-
signo = zval_get_long(user_signo);
731-
if (sigaddset(&set, signo) != 0) {
732-
PCNTL_G(last_error) = errno;
733-
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
734-
RETURN_FALSE;
735-
}
736-
} ZEND_HASH_FOREACH_END();
785+
sigset_t set;
786+
bool status = php_pcntl_set_user_signal_infos(user_set, &set, 2, /* allow_empty_signal_array */ how == SIG_SETMASK);
787+
/* Some error occurred */
788+
if (!status) {
789+
RETURN_FALSE;
790+
}
737791

738-
if (sigprocmask(how, &set, &oldset) != 0) {
792+
if (sigprocmask(how, &set, &old_set) != 0) {
739793
PCNTL_G(last_error) = errno;
740794
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
741795
RETURN_FALSE;
742796
}
743797

744-
if (user_oldset != NULL) {
745-
user_oldset = zend_try_array_init(user_oldset);
746-
if (!user_oldset) {
798+
if (user_old_set != NULL) {
799+
user_old_set = zend_try_array_init(user_old_set);
800+
if (!user_old_set) {
747801
RETURN_THROWS();
748802
}
749803

750-
for (signo = 1; signo < PCNTL_G(num_signals); ++signo) {
751-
if (sigismember(&oldset, signo) != 1) {
804+
for (int signal_no = 1; signal_no < PCNTL_G(num_signals); ++signal_no) {
805+
if (sigismember(&old_set, signal_no) != 1) {
752806
continue;
753807
}
754-
add_next_index_long(user_oldset, signo);
808+
add_next_index_long(user_old_set, signal_no);
755809
}
756810
}
757811

@@ -761,82 +815,109 @@ PHP_FUNCTION(pcntl_sigprocmask)
761815
#endif
762816

763817
#ifdef HAVE_STRUCT_SIGINFO_T
764-
# if defined(HAVE_SIGWAITINFO) && defined(HAVE_SIGTIMEDWAIT)
765-
static void pcntl_sigwaitinfo(INTERNAL_FUNCTION_PARAMETERS, int timedwait) /* {{{ */
818+
# ifdef HAVE_SIGWAITINFO
819+
820+
/* {{{ Synchronously wait for queued signals */
821+
PHP_FUNCTION(pcntl_sigwaitinfo)
766822
{
767-
zval *user_set, *user_signo, *user_siginfo = NULL;
768-
zend_long tv_sec = 0, tv_nsec = 0;
769-
sigset_t set;
770-
int signo;
771-
siginfo_t siginfo;
772-
struct timespec timeout;
773-
774-
if (timedwait) {
775-
ZEND_PARSE_PARAMETERS_START(1, 4)
776-
Z_PARAM_ARRAY(user_set)
777-
Z_PARAM_OPTIONAL
778-
Z_PARAM_ZVAL(user_siginfo)
779-
Z_PARAM_LONG(tv_sec)
780-
Z_PARAM_LONG(tv_nsec)
781-
ZEND_PARSE_PARAMETERS_END();
782-
} else {
783-
ZEND_PARSE_PARAMETERS_START(1, 2)
784-
Z_PARAM_ARRAY(user_set)
785-
Z_PARAM_OPTIONAL
786-
Z_PARAM_ZVAL(user_siginfo)
787-
ZEND_PARSE_PARAMETERS_END();
788-
}
823+
HashTable *user_set;
824+
/* Optional by-ref array of ints */
825+
zval *user_siginfo = NULL;
789826

790-
if (sigemptyset(&set) != 0) {
791-
PCNTL_G(last_error) = errno;
792-
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
827+
ZEND_PARSE_PARAMETERS_START(1, 2)
828+
Z_PARAM_ARRAY_HT(user_set)
829+
Z_PARAM_OPTIONAL
830+
Z_PARAM_ZVAL(user_siginfo)
831+
ZEND_PARSE_PARAMETERS_END();
832+
833+
sigset_t set;
834+
bool status = php_pcntl_set_user_signal_infos(user_set, &set, 1, /* allow_empty_signal_array */ false);
835+
/* Some error occurred */
836+
if (!status) {
793837
RETURN_FALSE;
794838
}
795839

796-
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(user_set), user_signo) {
797-
signo = zval_get_long(user_signo);
798-
if (sigaddset(&set, signo) != 0) {
799-
PCNTL_G(last_error) = errno;
800-
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
801-
RETURN_FALSE;
802-
}
803-
} ZEND_HASH_FOREACH_END();
804-
805-
if (timedwait) {
806-
timeout.tv_sec = (time_t) tv_sec;
807-
timeout.tv_nsec = tv_nsec;
808-
signo = sigtimedwait(&set, &siginfo, &timeout);
809-
} else {
810-
signo = sigwaitinfo(&set, &siginfo);
811-
}
812-
if (signo == -1 && errno != EAGAIN) {
840+
errno = 0;
841+
siginfo_t siginfo;
842+
int signal_no = sigwaitinfo(&set, &siginfo);
843+
/* sigwaitinfo() never sets errno to EAGAIN according to POSIX */
844+
if (signal_no == -1) {
813845
PCNTL_G(last_error) = errno;
814846
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
847+
RETURN_FALSE;
815848
}
816849

817-
/*
818-
* sigtimedwait and sigwaitinfo can return 0 on success on some
819-
* platforms, e.g. NetBSD
820-
*/
821-
if (!signo && siginfo.si_signo) {
822-
signo = siginfo.si_signo;
850+
/* sigwaitinfo can return 0 on success on some platforms, e.g. NetBSD */
851+
if (!signal_no && siginfo.si_signo) {
852+
signal_no = siginfo.si_signo;
823853
}
824-
pcntl_siginfo_to_zval(signo, &siginfo, user_siginfo);
825-
RETURN_LONG(signo);
826-
}
827-
/* }}} */
828854

829-
/* {{{ Synchronously wait for queued signals */
830-
PHP_FUNCTION(pcntl_sigwaitinfo)
831-
{
832-
pcntl_sigwaitinfo(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
855+
pcntl_siginfo_to_zval(signal_no, &siginfo, user_siginfo);
856+
857+
RETURN_LONG(signal_no);
833858
}
834859
/* }}} */
835-
860+
# endif
861+
# ifdef HAVE_SIGTIMEDWAIT
836862
/* {{{ Wait for queued signals */
837863
PHP_FUNCTION(pcntl_sigtimedwait)
838864
{
839-
pcntl_sigwaitinfo(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
865+
HashTable *user_set;
866+
/* Optional by-ref array of ints */
867+
zval *user_siginfo = NULL;
868+
zend_long tv_sec = 0;
869+
zend_long tv_nsec = 0;
870+
871+
ZEND_PARSE_PARAMETERS_START(1, 4)
872+
Z_PARAM_ARRAY_HT(user_set)
873+
Z_PARAM_OPTIONAL
874+
Z_PARAM_ZVAL(user_siginfo)
875+
Z_PARAM_LONG(tv_sec)
876+
Z_PARAM_LONG(tv_nsec)
877+
ZEND_PARSE_PARAMETERS_END();
878+
879+
sigset_t set;
880+
bool status = php_pcntl_set_user_signal_infos(user_set, &set, 1, /* allow_empty_signal_array */ false);
881+
/* Some error occurred */
882+
if (!status) {
883+
RETURN_FALSE;
884+
}
885+
if (tv_sec < 0) {
886+
zend_argument_value_error(3, "must be greater than or equal to 0");
887+
RETURN_THROWS();
888+
}
889+
/* Nanosecond between 0 and 1e9 */
890+
if (tv_nsec < 0 || tv_nsec >= 1000000000) {
891+
zend_argument_value_error(4, "must be between 0 and 1e9");
892+
RETURN_THROWS();
893+
}
894+
if (UNEXPECTED(tv_sec == 0 && tv_nsec == 0)) {
895+
zend_value_error("pcntl_sigtimedwait(): At least one of argument #3 ($seconds) or argument #4 ($nanoseconds) must be greater than 0");
896+
RETURN_THROWS();
897+
}
898+
899+
errno = 0;
900+
siginfo_t siginfo;
901+
struct timespec timeout;
902+
timeout.tv_sec = (time_t) tv_sec;
903+
timeout.tv_nsec = tv_nsec;
904+
int signal_no = sigtimedwait(&set, &siginfo, &timeout);
905+
if (signal_no == -1) {
906+
if (errno != EAGAIN) {
907+
PCNTL_G(last_error) = errno;
908+
php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
909+
}
910+
RETURN_FALSE;
911+
}
912+
913+
/* sigtimedwait can return 0 on success on some platforms, e.g. NetBSD */
914+
if (!signal_no && siginfo.si_signo) {
915+
signal_no = siginfo.si_signo;
916+
}
917+
918+
pcntl_siginfo_to_zval(signal_no, &siginfo, user_siginfo);
919+
920+
RETURN_LONG(signal_no);
840921
}
841922
/* }}} */
842923
# endif

ext/pcntl/tests/002.phpt

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ 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');
10-
elseif (getenv('SKIP_ASAN')) die('skip Fails intermittently under asan/msan');
1114
elseif (getenv("SKIP_REPEAT")) die("skip cannot be repeated");
1215
elseif (str_contains(PHP_OS, 'FreeBSD')) die('skip Results in parallel test runner hang on FreeBSD');
1316
?>
@@ -20,7 +23,7 @@ if ($pid == -1) {
2023
} else if ($pid) {
2124
pcntl_sigprocmask(SIG_BLOCK, array(SIGCHLD,(string)SIGTERM));
2225
$oldset = array();
23-
pcntl_sigprocmask(SIG_BLOCK, array(), $oldset);
26+
pcntl_sigprocmask(SIG_UNBLOCK, array(SIGINT), $oldset);
2427
var_dump(in_array(SIGCHLD, $oldset));
2528
var_dump(in_array(SIGTERM, $oldset));
2629

@@ -49,27 +52,6 @@ if ($pid == -1) {
4952
echo "signo === pid\n";
5053
var_dump($siginfo['pid'] === $pid);
5154
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)));
67-
68-
echo "sigwaitinfo with invalid arguments\n";
69-
var_dump(pcntl_sigwaitinfo(array(0)));
70-
71-
echo "sigtimedwait with invalid arguments\n";
72-
var_dump(pcntl_sigtimedwait(array(SIGTERM), $signo, PHP_INT_MAX, PHP_INT_MAX));
7355
} else {
7456
$siginfo = NULL;
7557
pcntl_sigtimedwait(array(SIGINT), $siginfo, 3600, 0);
@@ -94,14 +76,3 @@ signo === uid
9476
bool(true)
9577
signo === pid
9678
bool(true)
97-
sigprocmask with invalid arguments
98-
Error triggered
99-
bool(false)
100-
Error triggered
101-
bool(false)
102-
sigwaitinfo with invalid arguments
103-
Error triggered
104-
bool(false)
105-
sigtimedwait with invalid arguments
106-
Error triggered
107-
int(-1)

0 commit comments

Comments
 (0)