1 /*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | https://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Author: Jason Greene <jason@inetgurus.net> |
14 +----------------------------------------------------------------------+
15 */
16
17 #define PCNTL_DEBUG 0
18
19 #if PCNTL_DEBUG
20 #define DEBUG_OUT printf("DEBUG: ");printf
21 #define IF_DEBUG(z) z
22 #else
23 #define IF_DEBUG(z)
24 #endif
25
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29
30 #include "php.h"
31 #include "php_ini.h"
32 #include "ext/standard/info.h"
33 #include "php_pcntl.h"
34 #include "php_signal.h"
35 #include "php_ticks.h"
36 #include "zend_fibers.h"
37
38 #if defined(HAVE_GETPRIORITY) || defined(HAVE_SETPRIORITY) || defined(HAVE_WAIT3)
39 #include <sys/wait.h>
40 #include <sys/time.h>
41 #include <sys/resource.h>
42 #endif
43
44 #include <errno.h>
45 #if defined(HAVE_UNSHARE) || defined(HAVE_SCHED_SETAFFINITY) || defined(HAVE_SCHED_GETCPU)
46 #include <sched.h>
47 #if defined(__FreeBSD__)
48 #include <sys/types.h>
49 #include <sys/cpuset.h>
50 typedef cpuset_t cpu_set_t;
51 #endif
52 #endif
53
54 #if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP)
55 #include <pthread/qos.h>
56 static zend_class_entry *QosClass_ce;
57 #endif
58
59 #ifdef HAVE_PIDFD_OPEN
60 #include <sys/syscall.h>
61 #endif
62
63 #ifdef HAVE_FORKX
64 #include <sys/fork.h>
65 #endif
66
67 #ifndef NSIG
68 # define NSIG 32
69 #endif
70
71 #define LONG_CONST(c) (zend_long) c
72
73 #include "Zend/zend_enum.h"
74 #include "Zend/zend_max_execution_timer.h"
75
76 #include "pcntl_arginfo.h"
77
78 ZEND_DECLARE_MODULE_GLOBALS(pcntl)
79 static PHP_GINIT_FUNCTION(pcntl);
80
81 zend_module_entry pcntl_module_entry = {
82 STANDARD_MODULE_HEADER,
83 "pcntl",
84 ext_functions,
85 PHP_MINIT(pcntl),
86 PHP_MSHUTDOWN(pcntl),
87 PHP_RINIT(pcntl),
88 PHP_RSHUTDOWN(pcntl),
89 PHP_MINFO(pcntl),
90 PHP_PCNTL_VERSION,
91 PHP_MODULE_GLOBALS(pcntl),
92 PHP_GINIT(pcntl),
93 NULL,
94 NULL,
95 STANDARD_MODULE_PROPERTIES_EX
96 };
97
98 #ifdef COMPILE_DL_PCNTL
99 #ifdef ZTS
100 ZEND_TSRMLS_CACHE_DEFINE()
101 #endif
102 ZEND_GET_MODULE(pcntl)
103 #endif
104
105 static void (*orig_interrupt_function)(zend_execute_data *execute_data);
106
107 #ifdef HAVE_STRUCT_SIGINFO_T
108 static void pcntl_signal_handler(int, siginfo_t*, void*);
109 static void pcntl_siginfo_to_zval(int, siginfo_t*, zval*);
110 #else
111 static void pcntl_signal_handler(int);
112 #endif
113 static void pcntl_signal_dispatch(void);
114 static void pcntl_signal_dispatch_tick_function(int dummy_int, void *dummy_pointer);
115 static void pcntl_interrupt_function(zend_execute_data *execute_data);
116
PHP_GINIT_FUNCTION(pcntl)117 static PHP_GINIT_FUNCTION(pcntl)
118 {
119 #if defined(COMPILE_DL_PCNTL) && defined(ZTS)
120 ZEND_TSRMLS_CACHE_UPDATE();
121 #endif
122 memset(pcntl_globals, 0, sizeof(*pcntl_globals));
123 }
124
PHP_RINIT_FUNCTION(pcntl)125 PHP_RINIT_FUNCTION(pcntl)
126 {
127 php_add_tick_function(pcntl_signal_dispatch_tick_function, NULL);
128 zend_hash_init(&PCNTL_G(php_signal_table), 16, NULL, ZVAL_PTR_DTOR, 0);
129 PCNTL_G(head) = PCNTL_G(tail) = PCNTL_G(spares) = NULL;
130 PCNTL_G(async_signals) = 0;
131 PCNTL_G(last_error) = 0;
132 PCNTL_G(num_signals) = NSIG;
133 #ifdef SIGRTMAX
134 /* At least FreeBSD reports an incorrecrt NSIG that does not include realtime signals.
135 * As SIGRTMAX may be a dynamic value, adjust the value in INIT. */
136 if (NSIG < SIGRTMAX + 1) {
137 PCNTL_G(num_signals) = SIGRTMAX + 1;
138 }
139 #endif
140 return SUCCESS;
141 }
142
PHP_MINIT_FUNCTION(pcntl)143 PHP_MINIT_FUNCTION(pcntl)
144 {
145 #if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP)
146 QosClass_ce = register_class_QosClass();
147 #endif
148 register_pcntl_symbols(module_number);
149 orig_interrupt_function = zend_interrupt_function;
150 zend_interrupt_function = pcntl_interrupt_function;
151
152 return SUCCESS;
153 }
154
PHP_MSHUTDOWN_FUNCTION(pcntl)155 PHP_MSHUTDOWN_FUNCTION(pcntl)
156 {
157 return SUCCESS;
158 }
159
PHP_RSHUTDOWN_FUNCTION(pcntl)160 PHP_RSHUTDOWN_FUNCTION(pcntl)
161 {
162 struct php_pcntl_pending_signal *sig;
163 zend_long signo;
164 zval *handle;
165
166 /* Reset all signals to their default disposition */
167 ZEND_HASH_FOREACH_NUM_KEY_VAL(&PCNTL_G(php_signal_table), signo, handle) {
168 if (Z_TYPE_P(handle) != IS_LONG || Z_LVAL_P(handle) != (zend_long)SIG_DFL) {
169 php_signal(signo, (Sigfunc *)(zend_long)SIG_DFL, 0);
170 }
171 } ZEND_HASH_FOREACH_END();
172
173 zend_hash_destroy(&PCNTL_G(php_signal_table));
174
175 while (PCNTL_G(head)) {
176 sig = PCNTL_G(head);
177 PCNTL_G(head) = sig->next;
178 efree(sig);
179 }
180 while (PCNTL_G(spares)) {
181 sig = PCNTL_G(spares);
182 PCNTL_G(spares) = sig->next;
183 efree(sig);
184 }
185
186 return SUCCESS;
187 }
188
PHP_MINFO_FUNCTION(pcntl)189 PHP_MINFO_FUNCTION(pcntl)
190 {
191 php_info_print_table_start();
192 php_info_print_table_row(2, "pcntl support", "enabled");
193 php_info_print_table_end();
194 }
195
196 /* {{{ Forks the currently running process following the same behavior as the UNIX fork() system call*/
PHP_FUNCTION(pcntl_fork)197 PHP_FUNCTION(pcntl_fork)
198 {
199 pid_t id;
200
201 ZEND_PARSE_PARAMETERS_NONE();
202
203 id = fork();
204 if (id == -1) {
205 PCNTL_G(last_error) = errno;
206 switch (errno) {
207 case EAGAIN:
208 php_error_docref(NULL, E_WARNING, "Error %d: Reached the maximum limit of number of processes", errno);
209 break;
210 case ENOMEM:
211 php_error_docref(NULL, E_WARNING, "Error %d: Insufficient memory", errno);
212 break;
213 // unlikely, especially nowadays.
214 case ENOSYS:
215 php_error_docref(NULL, E_WARNING, "Error %d: Unimplemented", errno);
216 break;
217 // QNX is the only platform returning it so far and is a different case from EAGAIN.
218 // Retries can be handled with sleep eventually.
219 case EBADF:
220 php_error_docref(NULL, E_WARNING, "Error %d: File descriptor concurrency issue", errno);
221 break;
222 default:
223 php_error_docref(NULL, E_WARNING, "Error %d", errno);
224
225 }
226 } else if (id == 0) {
227 zend_max_execution_timer_init();
228 }
229
230 RETURN_LONG((zend_long) id);
231 }
232 /* }}} */
233
234 /* {{{ Set an alarm clock for delivery of a signal*/
PHP_FUNCTION(pcntl_alarm)235 PHP_FUNCTION(pcntl_alarm)
236 {
237 zend_long seconds;
238
239 ZEND_PARSE_PARAMETERS_START(1, 1)
240 Z_PARAM_LONG(seconds);
241 ZEND_PARSE_PARAMETERS_END();
242
243 RETURN_LONG((zend_long) alarm(seconds));
244 }
245 /* }}} */
246
247 #define PHP_RUSAGE_PARA(from, to, field) \
248 add_assoc_long(to, #field, from.field)
249 #ifndef _OSD_POSIX
250 #define PHP_RUSAGE_SPECIAL(from, to) \
251 PHP_RUSAGE_PARA(from, to, ru_oublock); \
252 PHP_RUSAGE_PARA(from, to, ru_inblock); \
253 PHP_RUSAGE_PARA(from, to, ru_msgsnd); \
254 PHP_RUSAGE_PARA(from, to, ru_msgrcv); \
255 PHP_RUSAGE_PARA(from, to, ru_maxrss); \
256 PHP_RUSAGE_PARA(from, to, ru_ixrss); \
257 PHP_RUSAGE_PARA(from, to, ru_idrss); \
258 PHP_RUSAGE_PARA(from, to, ru_minflt); \
259 PHP_RUSAGE_PARA(from, to, ru_majflt); \
260 PHP_RUSAGE_PARA(from, to, ru_nsignals); \
261 PHP_RUSAGE_PARA(from, to, ru_nvcsw); \
262 PHP_RUSAGE_PARA(from, to, ru_nivcsw); \
263 PHP_RUSAGE_PARA(from, to, ru_nswap);
264 #else /*_OSD_POSIX*/
265 #define PHP_RUSAGE_SPECIAL(from, to)
266 #endif
267
268 #define PHP_RUSAGE_COMMON(from ,to) \
269 PHP_RUSAGE_PARA(from, to, ru_utime.tv_usec); \
270 PHP_RUSAGE_PARA(from, to, ru_utime.tv_sec); \
271 PHP_RUSAGE_PARA(from, to, ru_stime.tv_usec); \
272 PHP_RUSAGE_PARA(from, to, ru_stime.tv_sec);
273
274 #define PHP_RUSAGE_TO_ARRAY(from, to) \
275 if (to) { \
276 PHP_RUSAGE_SPECIAL(from, to) \
277 PHP_RUSAGE_COMMON(from, to); \
278 }
279
280 /* {{{ Waits on or returns the status of a forked child as defined by the waitpid() system call */
PHP_FUNCTION(pcntl_waitpid)281 PHP_FUNCTION(pcntl_waitpid)
282 {
283 zend_long pid, options = 0;
284 zval *z_status = NULL, *z_rusage = NULL;
285 int status;
286 pid_t child_id;
287 #ifdef HAVE_WAIT4
288 struct rusage rusage;
289 #endif
290
291 ZEND_PARSE_PARAMETERS_START(2, 4)
292 Z_PARAM_LONG(pid)
293 Z_PARAM_ZVAL(z_status)
294 Z_PARAM_OPTIONAL
295 Z_PARAM_LONG(options)
296 Z_PARAM_ZVAL(z_rusage)
297 ZEND_PARSE_PARAMETERS_END();
298
299 status = zval_get_long(z_status);
300
301 #ifdef HAVE_WAIT4
302 if (z_rusage) {
303 z_rusage = zend_try_array_init(z_rusage);
304 if (!z_rusage) {
305 RETURN_THROWS();
306 }
307
308 memset(&rusage, 0, sizeof(struct rusage));
309 child_id = wait4((pid_t) pid, &status, options, &rusage);
310 } else {
311 child_id = waitpid((pid_t) pid, &status, options);
312 }
313 #else
314 child_id = waitpid((pid_t) pid, &status, options);
315 #endif
316
317 if (child_id < 0) {
318 PCNTL_G(last_error) = errno;
319 }
320
321 #ifdef HAVE_WAIT4
322 if (child_id > 0) {
323 PHP_RUSAGE_TO_ARRAY(rusage, z_rusage);
324 }
325 #endif
326
327 ZEND_TRY_ASSIGN_REF_LONG(z_status, status);
328
329 RETURN_LONG((zend_long) child_id);
330 }
331 /* }}} */
332
333 /* {{{ Waits on or returns the status of a forked child as defined by the waitpid() system call */
PHP_FUNCTION(pcntl_wait)334 PHP_FUNCTION(pcntl_wait)
335 {
336 zend_long options = 0;
337 zval *z_status = NULL, *z_rusage = NULL;
338 int status;
339 pid_t child_id;
340 #ifdef HAVE_WAIT3
341 struct rusage rusage;
342 #endif
343
344 ZEND_PARSE_PARAMETERS_START(1, 3)
345 Z_PARAM_ZVAL(z_status)
346 Z_PARAM_OPTIONAL
347 Z_PARAM_LONG(options)
348 Z_PARAM_ZVAL(z_rusage)
349 ZEND_PARSE_PARAMETERS_END();
350
351 status = zval_get_long(z_status);
352 #ifdef HAVE_WAIT3
353 if (z_rusage) {
354 z_rusage = zend_try_array_init(z_rusage);
355 if (!z_rusage) {
356 RETURN_THROWS();
357 }
358
359 memset(&rusage, 0, sizeof(struct rusage));
360 child_id = wait3(&status, options, &rusage);
361 } else if (options) {
362 child_id = wait3(&status, options, NULL);
363 } else {
364 child_id = wait(&status);
365 }
366 #else
367 child_id = wait(&status);
368 #endif
369 if (child_id < 0) {
370 PCNTL_G(last_error) = errno;
371 }
372
373 #ifdef HAVE_WAIT3
374 if (child_id > 0) {
375 PHP_RUSAGE_TO_ARRAY(rusage, z_rusage);
376 }
377 #endif
378
379 ZEND_TRY_ASSIGN_REF_LONG(z_status, status);
380
381 RETURN_LONG((zend_long) child_id);
382 }
383 /* }}} */
384
385 #undef PHP_RUSAGE_PARA
386 #undef PHP_RUSAGE_SPECIAL
387 #undef PHP_RUSAGE_COMMON
388 #undef PHP_RUSAGE_TO_ARRAY
389
390 /* {{{ Returns true if the child status code represents a successful exit */
PHP_FUNCTION(pcntl_wifexited)391 PHP_FUNCTION(pcntl_wifexited)
392 {
393 zend_long status_word;
394
395 ZEND_PARSE_PARAMETERS_START(1, 1)
396 Z_PARAM_LONG(status_word)
397 ZEND_PARSE_PARAMETERS_END();
398
399 #ifdef WIFEXITED
400 int int_status_word = (int) status_word;
401 if (WIFEXITED(int_status_word)) {
402 RETURN_TRUE;
403 }
404 #endif
405
406 RETURN_FALSE;
407 }
408 /* }}} */
409
410 /* {{{ Returns true if the child status code represents a stopped process (WUNTRACED must have been used with waitpid) */
PHP_FUNCTION(pcntl_wifstopped)411 PHP_FUNCTION(pcntl_wifstopped)
412 {
413 zend_long status_word;
414
415 ZEND_PARSE_PARAMETERS_START(1, 1)
416 Z_PARAM_LONG(status_word)
417 ZEND_PARSE_PARAMETERS_END();
418
419 #ifdef WIFSTOPPED
420 int int_status_word = (int) status_word;
421 if (WIFSTOPPED(int_status_word)) {
422 RETURN_TRUE;
423 }
424 #endif
425
426 RETURN_FALSE;
427 }
428 /* }}} */
429
430 /* {{{ Returns true if the child status code represents a process that was terminated due to a signal */
PHP_FUNCTION(pcntl_wifsignaled)431 PHP_FUNCTION(pcntl_wifsignaled)
432 {
433 zend_long status_word;
434
435 ZEND_PARSE_PARAMETERS_START(1, 1)
436 Z_PARAM_LONG(status_word)
437 ZEND_PARSE_PARAMETERS_END();
438
439 #ifdef WIFSIGNALED
440 int int_status_word = (int) status_word;
441 if (WIFSIGNALED(int_status_word)) {
442 RETURN_TRUE;
443 }
444 #endif
445
446 RETURN_FALSE;
447 }
448 /* }}} */
449
450 /* {{{ Returns true if the child status code represents a process that was resumed due to a SIGCONT signal */
PHP_FUNCTION(pcntl_wifcontinued)451 PHP_FUNCTION(pcntl_wifcontinued)
452 {
453 zend_long status_word;
454
455 ZEND_PARSE_PARAMETERS_START(1, 1)
456 Z_PARAM_LONG(status_word)
457 ZEND_PARSE_PARAMETERS_END();
458
459 #ifdef HAVE_WCONTINUED
460 int int_status_word = (int) status_word;
461 if (WIFCONTINUED(int_status_word)) {
462 RETURN_TRUE;
463 }
464 #endif
465 RETURN_FALSE;
466 }
467 /* }}} */
468
469
470 /* {{{ Returns the status code of a child's exit */
PHP_FUNCTION(pcntl_wexitstatus)471 PHP_FUNCTION(pcntl_wexitstatus)
472 {
473 zend_long status_word;
474
475 ZEND_PARSE_PARAMETERS_START(1, 1)
476 Z_PARAM_LONG(status_word)
477 ZEND_PARSE_PARAMETERS_END();
478
479 #ifdef WEXITSTATUS
480 int int_status_word = (int) status_word;
481 RETURN_LONG(WEXITSTATUS(int_status_word));
482 #else
483 RETURN_FALSE;
484 #endif
485 }
486 /* }}} */
487
488 /* {{{ Returns the number of the signal that terminated the process who's status code is passed */
PHP_FUNCTION(pcntl_wtermsig)489 PHP_FUNCTION(pcntl_wtermsig)
490 {
491 zend_long status_word;
492
493 ZEND_PARSE_PARAMETERS_START(1, 1)
494 Z_PARAM_LONG(status_word)
495 ZEND_PARSE_PARAMETERS_END();
496
497 #ifdef WTERMSIG
498 int int_status_word = (int) status_word;
499 RETURN_LONG(WTERMSIG(int_status_word));
500 #else
501 RETURN_FALSE;
502 #endif
503 }
504 /* }}} */
505
506 /* {{{ Returns the number of the signal that caused the process to stop who's status code is passed */
PHP_FUNCTION(pcntl_wstopsig)507 PHP_FUNCTION(pcntl_wstopsig)
508 {
509 zend_long status_word;
510
511 ZEND_PARSE_PARAMETERS_START(1, 1)
512 Z_PARAM_LONG(status_word)
513 ZEND_PARSE_PARAMETERS_END();
514
515 #ifdef WSTOPSIG
516 int int_status_word = (int) status_word;
517 RETURN_LONG(WSTOPSIG(int_status_word));
518 #else
519 RETURN_FALSE;
520 #endif
521 }
522 /* }}} */
523
524 /* {{{ Executes specified program in current process space as defined by exec(2) */
PHP_FUNCTION(pcntl_exec)525 PHP_FUNCTION(pcntl_exec)
526 {
527 zval *args = NULL, *envs = NULL;
528 zval *element;
529 HashTable *args_hash, *envs_hash;
530 int argc = 0, argi = 0;
531 int envc = 0, envi = 0;
532 char **argv = NULL, **envp = NULL;
533 char **current_arg, **pair;
534 size_t pair_length;
535 zend_string *key;
536 char *path;
537 size_t path_len;
538 zend_ulong key_num;
539
540 ZEND_PARSE_PARAMETERS_START(1, 3)
541 Z_PARAM_PATH(path, path_len)
542 Z_PARAM_OPTIONAL
543 Z_PARAM_ARRAY(args)
544 Z_PARAM_ARRAY(envs)
545 ZEND_PARSE_PARAMETERS_END();
546
547 if (ZEND_NUM_ARGS() > 1) {
548 /* Build argument list */
549 SEPARATE_ARRAY(args);
550 args_hash = Z_ARRVAL_P(args);
551 argc = zend_hash_num_elements(args_hash);
552
553 argv = safe_emalloc((argc + 2), sizeof(char *), 0);
554 *argv = path;
555 current_arg = argv+1;
556 ZEND_HASH_FOREACH_VAL(args_hash, element) {
557 if (argi >= argc) break;
558 if (!try_convert_to_string(element)) {
559 efree(argv);
560 RETURN_THROWS();
561 }
562
563 *current_arg = Z_STRVAL_P(element);
564 argi++;
565 current_arg++;
566 } ZEND_HASH_FOREACH_END();
567 *current_arg = NULL;
568 } else {
569 argv = emalloc(2 * sizeof(char *));
570 argv[0] = path;
571 argv[1] = NULL;
572 }
573
574 if ( ZEND_NUM_ARGS() == 3 ) {
575 /* Build environment pair list */
576 SEPARATE_ARRAY(envs);
577 envs_hash = Z_ARRVAL_P(envs);
578 envc = zend_hash_num_elements(envs_hash);
579
580 pair = envp = safe_emalloc((envc + 1), sizeof(char *), 0);
581 ZEND_HASH_FOREACH_KEY_VAL(envs_hash, key_num, key, element) {
582 if (envi >= envc) break;
583 if (!key) {
584 key = zend_long_to_str(key_num);
585 } else {
586 zend_string_addref(key);
587 }
588
589 if (!try_convert_to_string(element)) {
590 zend_string_release(key);
591 efree(argv);
592 efree(envp);
593 RETURN_THROWS();
594 }
595
596 /* Length of element + equal sign + length of key + null */
597 ZEND_ASSERT(Z_STRLEN_P(element) < SIZE_MAX && ZSTR_LEN(key) < SIZE_MAX);
598 *pair = safe_emalloc(Z_STRLEN_P(element) + 1, sizeof(char), ZSTR_LEN(key) + 1);
599 pair_length = Z_STRLEN_P(element) + ZSTR_LEN(key) + 2;
600 strlcpy(*pair, ZSTR_VAL(key), ZSTR_LEN(key) + 1);
601 strlcat(*pair, "=", pair_length);
602 strlcat(*pair, Z_STRVAL_P(element), pair_length);
603
604 /* Cleanup */
605 zend_string_release_ex(key, 0);
606 envi++;
607 pair++;
608 } ZEND_HASH_FOREACH_END();
609 *(pair) = NULL;
610
611 if (execve(path, argv, envp) == -1) {
612 PCNTL_G(last_error) = errno;
613 php_error_docref(NULL, E_WARNING, "Error has occurred: (errno %d) %s", errno, strerror(errno));
614 }
615
616 /* Cleanup */
617 for (pair = envp; *pair != NULL; pair++) efree(*pair);
618 efree(envp);
619 } else {
620
621 if (execv(path, argv) == -1) {
622 PCNTL_G(last_error) = errno;
623 php_error_docref(NULL, E_WARNING, "Error has occurred: (errno %d) %s", errno, strerror(errno));
624 }
625 }
626
627 efree(argv);
628
629 RETURN_FALSE;
630 }
631 /* }}} */
632
633 /* {{{ Assigns a system signal handler to a PHP function */
PHP_FUNCTION(pcntl_signal)634 PHP_FUNCTION(pcntl_signal)
635 {
636 zval *handle;
637 zend_long signo;
638 bool restart_syscalls = 1;
639 bool restart_syscalls_is_null = 1;
640
641 ZEND_PARSE_PARAMETERS_START(2, 3)
642 Z_PARAM_LONG(signo)
643 Z_PARAM_ZVAL(handle)
644 Z_PARAM_OPTIONAL
645 Z_PARAM_BOOL_OR_NULL(restart_syscalls, restart_syscalls_is_null)
646 ZEND_PARSE_PARAMETERS_END();
647
648 if (signo < 1) {
649 zend_argument_value_error(1, "must be greater than or equal to 1");
650 RETURN_THROWS();
651 }
652
653 if (signo >= PCNTL_G(num_signals)) {
654 zend_argument_value_error(1, "must be less than %d", PCNTL_G(num_signals));
655 RETURN_THROWS();
656 }
657
658 if (!PCNTL_G(spares)) {
659 /* since calling malloc() from within a signal handler is not portable,
660 * pre-allocate a few records for recording signals */
661 int i;
662 for (i = 0; i < PCNTL_G(num_signals); i++) {
663 struct php_pcntl_pending_signal *psig;
664
665 psig = emalloc(sizeof(*psig));
666 psig->next = PCNTL_G(spares);
667 PCNTL_G(spares) = psig;
668 }
669 }
670
671 /* If restart_syscalls was not explicitly specified and the signal is SIGALRM, then default
672 * restart_syscalls to false. PHP used to enforce that restart_syscalls is false for SIGALRM,
673 * so we keep this differing default to reduce the degree of BC breakage. */
674 if (restart_syscalls_is_null && signo == SIGALRM) {
675 restart_syscalls = 0;
676 }
677
678 /* Special long value case for SIG_DFL and SIG_IGN */
679 if (Z_TYPE_P(handle) == IS_LONG) {
680 if (Z_LVAL_P(handle) != (zend_long) SIG_DFL && Z_LVAL_P(handle) != (zend_long) SIG_IGN) {
681 zend_argument_value_error(2, "must be either SIG_DFL or SIG_IGN when an integer value is given");
682 RETURN_THROWS();
683 }
684 if (php_signal(signo, (Sigfunc *) Z_LVAL_P(handle), (int) restart_syscalls) == (void *)SIG_ERR) {
685 PCNTL_G(last_error) = errno;
686 php_error_docref(NULL, E_WARNING, "Error assigning signal");
687 RETURN_FALSE;
688 }
689 zend_hash_index_update(&PCNTL_G(php_signal_table), signo, handle);
690 RETURN_TRUE;
691 }
692
693 if (!zend_is_callable_ex(handle, NULL, 0, NULL, NULL, NULL)) {
694 PCNTL_G(last_error) = EINVAL;
695
696 zend_argument_type_error(2, "must be of type callable|int, %s given", zend_zval_value_name(handle));
697 RETURN_THROWS();
698 }
699
700 /* Add the function name to our signal table */
701 handle = zend_hash_index_update(&PCNTL_G(php_signal_table), signo, handle);
702 Z_TRY_ADDREF_P(handle);
703
704 if (php_signal4(signo, pcntl_signal_handler, (int) restart_syscalls, 1) == (void *)SIG_ERR) {
705 PCNTL_G(last_error) = errno;
706 php_error_docref(NULL, E_WARNING, "Error assigning signal");
707 RETURN_FALSE;
708 }
709 RETURN_TRUE;
710 }
711 /* }}} */
712
713 /* {{{ Gets signal handler */
PHP_FUNCTION(pcntl_signal_get_handler)714 PHP_FUNCTION(pcntl_signal_get_handler)
715 {
716 zval *prev_handle;
717 zend_long signo;
718
719 ZEND_PARSE_PARAMETERS_START(1, 1)
720 Z_PARAM_LONG(signo)
721 ZEND_PARSE_PARAMETERS_END();
722
723 // note: max signal on mac is SIGUSR2 (31), no real time signals.
724 int sigmax = NSIG - 1;
725 #if defined(SIGRTMAX)
726 // oddily enough, NSIG on freebsd reports only 32 whereas SIGRTMIN starts at 65.
727 if (sigmax < SIGRTMAX) {
728 sigmax = SIGRTMAX;
729 }
730 #endif
731
732 if (signo < 1 || signo > sigmax) {
733 zend_argument_value_error(1, "must be between 1 and %d", sigmax);
734 RETURN_THROWS();
735 }
736
737 if ((prev_handle = zend_hash_index_find(&PCNTL_G(php_signal_table), signo)) != NULL) {
738 RETURN_COPY(prev_handle);
739 } else {
740 RETURN_LONG((zend_long)SIG_DFL);
741 }
742 }
743
744 /* {{{ Dispatch signals to signal handlers */
PHP_FUNCTION(pcntl_signal_dispatch)745 PHP_FUNCTION(pcntl_signal_dispatch)
746 {
747 ZEND_PARSE_PARAMETERS_NONE();
748
749 pcntl_signal_dispatch();
750 RETURN_TRUE;
751 }
752 /* }}} */
753
754 /* Common helper function for these 3 wrapper functions */
755 #if defined(HAVE_SIGWAITINFO) || defined(HAVE_SIGTIMEDWAIT) || defined(HAVE_SIGPROCMASK)
php_pcntl_set_user_signal_infos(HashTable * const user_signals,sigset_t * const set,size_t arg_num,bool allow_empty_signal_array)756 static bool php_pcntl_set_user_signal_infos(
757 /* const */ HashTable *const user_signals,
758 sigset_t *const set,
759 size_t arg_num,
760 bool allow_empty_signal_array
761 ) {
762 if (!allow_empty_signal_array && zend_hash_num_elements(user_signals) == 0) {
763 zend_argument_value_error(arg_num, "cannot be empty");
764 return false;
765 }
766
767 errno = 0;
768 if (sigemptyset(set) != 0) {
769 PCNTL_G(last_error) = errno;
770 php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
771 return false;
772 }
773
774 zval *user_signal_no;
775 ZEND_HASH_FOREACH_VAL(user_signals, user_signal_no) {
776 bool failed = true;
777 zend_long tmp = zval_try_get_long(user_signal_no, &failed);
778
779 if (failed) {
780 zend_argument_type_error(arg_num, "signals must be of type int, %s given", zend_zval_value_name(user_signal_no));
781 return false;
782 }
783 /* Signals are positive integers */
784 if (tmp < 1 || tmp >= PCNTL_G(num_signals)) {
785 /* PCNTL_G(num_signals) stores +1 from the last valid signal */
786 zend_argument_value_error(arg_num, "signals must be between 1 and %d", PCNTL_G(num_signals)-1);
787 return false;
788 }
789
790 int signal_no = (int) tmp;
791 errno = 0;
792 if (sigaddset(set, signal_no) != 0) {
793 PCNTL_G(last_error) = errno;
794 php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
795 return false;
796 }
797 } ZEND_HASH_FOREACH_END();
798 return true;
799 }
800 #endif
801
802 #ifdef HAVE_SIGPROCMASK
803 /* {{{ Examine and change blocked signals */
PHP_FUNCTION(pcntl_sigprocmask)804 PHP_FUNCTION(pcntl_sigprocmask)
805 {
806 zend_long how;
807 HashTable *user_set;
808 /* Optional by-ref out-param array of old signals */
809 zval *user_old_set = NULL;
810
811 ZEND_PARSE_PARAMETERS_START(2, 3)
812 Z_PARAM_LONG(how)
813 Z_PARAM_ARRAY_HT(user_set)
814 Z_PARAM_OPTIONAL
815 Z_PARAM_ZVAL(user_old_set)
816 ZEND_PARSE_PARAMETERS_END();
817
818 if (how != SIG_BLOCK && how != SIG_UNBLOCK && how != SIG_SETMASK) {
819 zend_argument_value_error(1, "must be one of SIG_BLOCK, SIG_UNBLOCK, or SIG_SETMASK");
820 RETURN_THROWS();
821 }
822
823 errno = 0;
824 sigset_t old_set;
825 if (sigemptyset(&old_set) != 0) {
826 PCNTL_G(last_error) = errno;
827 php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
828 RETURN_FALSE;
829 }
830
831 sigset_t set;
832 bool status = php_pcntl_set_user_signal_infos(user_set, &set, 2, /* allow_empty_signal_array */ how == SIG_SETMASK);
833 /* Some error occurred */
834 if (!status) {
835 RETURN_FALSE;
836 }
837
838 if (sigprocmask(how, &set, &old_set) != 0) {
839 PCNTL_G(last_error) = errno;
840 php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
841 RETURN_FALSE;
842 }
843
844 if (user_old_set != NULL) {
845 user_old_set = zend_try_array_init(user_old_set);
846 if (!user_old_set) {
847 RETURN_THROWS();
848 }
849
850 for (int signal_no = 1; signal_no < PCNTL_G(num_signals); ++signal_no) {
851 if (sigismember(&old_set, signal_no) != 1) {
852 continue;
853 }
854 add_next_index_long(user_old_set, signal_no);
855 }
856 }
857
858 RETURN_TRUE;
859 }
860 /* }}} */
861 #endif
862
863 #ifdef HAVE_STRUCT_SIGINFO_T
864 # ifdef HAVE_SIGWAITINFO
865
866 /* {{{ Synchronously wait for queued signals */
PHP_FUNCTION(pcntl_sigwaitinfo)867 PHP_FUNCTION(pcntl_sigwaitinfo)
868 {
869 HashTable *user_set;
870 /* Optional by-ref array of ints */
871 zval *user_siginfo = NULL;
872
873 ZEND_PARSE_PARAMETERS_START(1, 2)
874 Z_PARAM_ARRAY_HT(user_set)
875 Z_PARAM_OPTIONAL
876 Z_PARAM_ZVAL(user_siginfo)
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
886 errno = 0;
887 siginfo_t siginfo;
888 int signal_no = sigwaitinfo(&set, &siginfo);
889 /* sigwaitinfo() never sets errno to EAGAIN according to POSIX */
890 if (signal_no == -1) {
891 PCNTL_G(last_error) = errno;
892 php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
893 RETURN_FALSE;
894 }
895
896 /* sigwaitinfo can return 0 on success on some platforms, e.g. NetBSD */
897 if (!signal_no && siginfo.si_signo) {
898 signal_no = siginfo.si_signo;
899 }
900
901 pcntl_siginfo_to_zval(signal_no, &siginfo, user_siginfo);
902
903 RETURN_LONG(signal_no);
904 }
905 /* }}} */
906 # endif
907 # ifdef HAVE_SIGTIMEDWAIT
908 /* {{{ Wait for queued signals */
PHP_FUNCTION(pcntl_sigtimedwait)909 PHP_FUNCTION(pcntl_sigtimedwait)
910 {
911 HashTable *user_set;
912 /* Optional by-ref array of ints */
913 zval *user_siginfo = NULL;
914 zend_long tv_sec = 0;
915 zend_long tv_nsec = 0;
916
917 ZEND_PARSE_PARAMETERS_START(1, 4)
918 Z_PARAM_ARRAY_HT(user_set)
919 Z_PARAM_OPTIONAL
920 Z_PARAM_ZVAL(user_siginfo)
921 Z_PARAM_LONG(tv_sec)
922 Z_PARAM_LONG(tv_nsec)
923 ZEND_PARSE_PARAMETERS_END();
924
925 sigset_t set;
926 bool status = php_pcntl_set_user_signal_infos(user_set, &set, 1, /* allow_empty_signal_array */ false);
927 /* Some error occurred */
928 if (!status) {
929 RETURN_FALSE;
930 }
931 if (tv_sec < 0) {
932 zend_argument_value_error(3, "must be greater than or equal to 0");
933 RETURN_THROWS();
934 }
935 /* Nanosecond between 0 and 1e9 */
936 if (tv_nsec < 0 || tv_nsec >= 1000000000) {
937 zend_argument_value_error(4, "must be between 0 and 1e9");
938 RETURN_THROWS();
939 }
940 if (UNEXPECTED(tv_sec == 0 && tv_nsec == 0)) {
941 zend_value_error("pcntl_sigtimedwait(): At least one of argument #3 ($seconds) or argument #4 ($nanoseconds) must be greater than 0");
942 RETURN_THROWS();
943 }
944
945 errno = 0;
946 siginfo_t siginfo;
947 struct timespec timeout;
948 timeout.tv_sec = (time_t) tv_sec;
949 timeout.tv_nsec = tv_nsec;
950 int signal_no = sigtimedwait(&set, &siginfo, &timeout);
951 if (signal_no == -1) {
952 if (errno != EAGAIN) {
953 PCNTL_G(last_error) = errno;
954 php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
955 }
956 RETURN_FALSE;
957 }
958
959 /* sigtimedwait can return 0 on success on some platforms, e.g. NetBSD */
960 if (!signal_no && siginfo.si_signo) {
961 signal_no = siginfo.si_signo;
962 }
963
964 pcntl_siginfo_to_zval(signal_no, &siginfo, user_siginfo);
965
966 RETURN_LONG(signal_no);
967 }
968 /* }}} */
969 # endif
970
pcntl_siginfo_to_zval(int signo,siginfo_t * siginfo,zval * user_siginfo)971 static void pcntl_siginfo_to_zval(int signo, siginfo_t *siginfo, zval *user_siginfo) /* {{{ */
972 {
973 if (signo > 0 && user_siginfo) {
974 user_siginfo = zend_try_array_init(user_siginfo);
975 if (!user_siginfo) {
976 return;
977 }
978
979 add_assoc_long_ex(user_siginfo, "signo", sizeof("signo")-1, siginfo->si_signo);
980 add_assoc_long_ex(user_siginfo, "errno", sizeof("errno")-1, siginfo->si_errno);
981 add_assoc_long_ex(user_siginfo, "code", sizeof("code")-1, siginfo->si_code);
982 switch(signo) {
983 #ifdef SIGCHLD
984 case SIGCHLD:
985 add_assoc_long_ex(user_siginfo, "status", sizeof("status")-1, siginfo->si_status);
986 # ifdef si_utime
987 add_assoc_double_ex(user_siginfo, "utime", sizeof("utime")-1, siginfo->si_utime);
988 # endif
989 # ifdef si_stime
990 add_assoc_double_ex(user_siginfo, "stime", sizeof("stime")-1, siginfo->si_stime);
991 # endif
992 add_assoc_long_ex(user_siginfo, "pid", sizeof("pid")-1, siginfo->si_pid);
993 add_assoc_long_ex(user_siginfo, "uid", sizeof("uid")-1, siginfo->si_uid);
994 break;
995 case SIGUSR1:
996 case SIGUSR2:
997 add_assoc_long_ex(user_siginfo, "pid", sizeof("pid")-1, siginfo->si_pid);
998 add_assoc_long_ex(user_siginfo, "uid", sizeof("uid")-1, siginfo->si_uid);
999 break;
1000 #endif
1001 case SIGILL:
1002 case SIGFPE:
1003 case SIGSEGV:
1004 case SIGBUS:
1005 add_assoc_double_ex(user_siginfo, "addr", sizeof("addr")-1, (zend_long)siginfo->si_addr);
1006 break;
1007 #if defined(SIGPOLL) && !defined(__CYGWIN__)
1008 case SIGPOLL:
1009 add_assoc_long_ex(user_siginfo, "band", sizeof("band")-1, siginfo->si_band);
1010 # ifdef si_fd
1011 add_assoc_long_ex(user_siginfo, "fd", sizeof("fd")-1, siginfo->si_fd);
1012 # endif
1013 break;
1014 #endif
1015 }
1016 #if defined(SIGRTMIN) && defined(SIGRTMAX)
1017 if (SIGRTMIN <= signo && signo <= SIGRTMAX) {
1018 add_assoc_long_ex(user_siginfo, "pid", sizeof("pid")-1, siginfo->si_pid);
1019 add_assoc_long_ex(user_siginfo, "uid", sizeof("uid")-1, siginfo->si_uid);
1020 }
1021 #endif
1022 }
1023 }
1024 /* }}} */
1025 #endif
1026
1027 #ifdef HAVE_GETPRIORITY
1028 /* {{{ Get the priority of any process */
PHP_FUNCTION(pcntl_getpriority)1029 PHP_FUNCTION(pcntl_getpriority)
1030 {
1031 zend_long who = PRIO_PROCESS;
1032 zend_long pid;
1033 bool pid_is_null = 1;
1034 int pri;
1035
1036 ZEND_PARSE_PARAMETERS_START(0, 2)
1037 Z_PARAM_OPTIONAL
1038 Z_PARAM_LONG_OR_NULL(pid, pid_is_null)
1039 Z_PARAM_LONG(who)
1040 ZEND_PARSE_PARAMETERS_END();
1041
1042 /* needs to be cleared, since any returned value is valid */
1043 errno = 0;
1044
1045 pid = pid_is_null ? 0 : pid;
1046 pri = getpriority(who, pid);
1047
1048 if (errno) {
1049 PCNTL_G(last_error) = errno;
1050 switch (errno) {
1051 case ESRCH:
1052 php_error_docref(NULL, E_WARNING, "Error %d: No process was located using the given parameters", errno);
1053 break;
1054 case EINVAL:
1055 #ifdef PRIO_DARWIN_BG
1056 if (who != PRIO_PGRP && who != PRIO_USER && who != PRIO_PROCESS && who != PRIO_DARWIN_THREAD) {
1057 zend_argument_value_error(2, "must be one of PRIO_PGRP, PRIO_USER, PRIO_PROCESS or PRIO_DARWIN_THREAD");
1058 RETURN_THROWS();
1059 } else if (who == PRIO_DARWIN_THREAD && pid != 0) {
1060 zend_argument_value_error(1, "must be 0 (zero) if PRIO_DARWIN_THREAD is provided as second parameter");
1061 RETURN_THROWS();
1062 } else {
1063 zend_argument_value_error(1, "is not a valid process, process group, or user ID");
1064 RETURN_THROWS();
1065 }
1066 #else
1067 zend_argument_value_error(2, "must be one of PRIO_PGRP, PRIO_USER, or PRIO_PROCESS");
1068 RETURN_THROWS();
1069 #endif
1070
1071 default:
1072 php_error_docref(NULL, E_WARNING, "Unknown error %d has occurred", errno);
1073 break;
1074 }
1075 RETURN_FALSE;
1076 }
1077
1078 RETURN_LONG(pri);
1079 }
1080 /* }}} */
1081 #endif
1082
1083 #ifdef HAVE_SETPRIORITY
1084 /* {{{ Change the priority of any process */
PHP_FUNCTION(pcntl_setpriority)1085 PHP_FUNCTION(pcntl_setpriority)
1086 {
1087 zend_long who = PRIO_PROCESS;
1088 zend_long pid;
1089 bool pid_is_null = 1;
1090 zend_long pri;
1091
1092 ZEND_PARSE_PARAMETERS_START(1, 3)
1093 Z_PARAM_LONG(pri)
1094 Z_PARAM_OPTIONAL
1095 Z_PARAM_LONG_OR_NULL(pid, pid_is_null)
1096 Z_PARAM_LONG(who)
1097 ZEND_PARSE_PARAMETERS_END();
1098
1099 pid = pid_is_null ? 0 : pid;
1100
1101 if (setpriority(who, pid, pri)) {
1102 PCNTL_G(last_error) = errno;
1103 switch (errno) {
1104 case ESRCH:
1105 php_error_docref(NULL, E_WARNING, "Error %d: No process was located using the given parameters", errno);
1106 break;
1107 case EINVAL:
1108 #ifdef PRIO_DARWIN_BG
1109 if (who != PRIO_PGRP && who != PRIO_USER && who != PRIO_PROCESS && who != PRIO_DARWIN_THREAD) {
1110 zend_argument_value_error(3, "must be one of PRIO_PGRP, PRIO_USER, PRIO_PROCESS or PRIO_DARWIN_THREAD");
1111 RETURN_THROWS();
1112 } else if (who == PRIO_DARWIN_THREAD && pid != 0) {
1113 zend_argument_value_error(2, "must be 0 (zero) if PRIO_DARWIN_THREAD is provided as second parameter");
1114 RETURN_THROWS();
1115 } else if (who == PRIO_DARWIN_THREAD && pid == 0 && (pri != 0 && pri != PRIO_DARWIN_BG)) {
1116 zend_argument_value_error(1, "must be either 0 (zero) or PRIO_DARWIN_BG, for mode PRIO_DARWIN_THREAD");
1117 RETURN_THROWS();
1118 } else {
1119 zend_argument_value_error(2, "is not a valid process, process group, or user ID");
1120 RETURN_THROWS();
1121 }
1122 #else
1123 zend_argument_value_error(3, "must be one of PRIO_PGRP, PRIO_USER, or PRIO_PROCESS");
1124 RETURN_THROWS();
1125 #endif
1126 case EPERM:
1127 php_error_docref(NULL, E_WARNING, "Error %d: A process was located, but neither its effective nor real user ID matched the effective user ID of the caller", errno);
1128 break;
1129 case EACCES:
1130 php_error_docref(NULL, E_WARNING, "Error %d: Only a super user may attempt to increase the process priority", errno);
1131 break;
1132 default:
1133 php_error_docref(NULL, E_WARNING, "Unknown error %d has occurred", errno);
1134 break;
1135 }
1136 RETURN_FALSE;
1137 }
1138
1139 RETURN_TRUE;
1140 }
1141 /* }}} */
1142 #endif
1143
1144 /* {{{ Retrieve the error number set by the last pcntl function which failed. */
PHP_FUNCTION(pcntl_get_last_error)1145 PHP_FUNCTION(pcntl_get_last_error)
1146 {
1147 ZEND_PARSE_PARAMETERS_NONE();
1148
1149 RETURN_LONG(PCNTL_G(last_error));
1150 }
1151 /* }}} */
1152
1153 /* {{{ Retrieve the system error message associated with the given errno. */
PHP_FUNCTION(pcntl_strerror)1154 PHP_FUNCTION(pcntl_strerror)
1155 {
1156 zend_long error;
1157
1158 ZEND_PARSE_PARAMETERS_START(1, 1)
1159 Z_PARAM_LONG(error)
1160 ZEND_PARSE_PARAMETERS_END();
1161
1162 RETURN_STRING(strerror(error));
1163 }
1164 /* }}} */
1165
1166 /* Our custom signal handler that calls the appropriate php_function */
1167 #ifdef HAVE_STRUCT_SIGINFO_T
pcntl_signal_handler(int signo,siginfo_t * siginfo,void * context)1168 static void pcntl_signal_handler(int signo, siginfo_t *siginfo, void *context)
1169 #else
1170 static void pcntl_signal_handler(int signo)
1171 #endif
1172 {
1173 struct php_pcntl_pending_signal *psig = PCNTL_G(spares);
1174 if (!psig) {
1175 /* oops, too many signals for us to track, so we'll forget about this one */
1176 return;
1177 }
1178 PCNTL_G(spares) = psig->next;
1179
1180 psig->signo = signo;
1181 psig->next = NULL;
1182
1183 #ifdef HAVE_STRUCT_SIGINFO_T
1184 psig->siginfo = *siginfo;
1185 #endif
1186
1187 /* the head check is important, as the tick handler cannot atomically clear both
1188 * the head and tail */
1189 if (PCNTL_G(head) && PCNTL_G(tail)) {
1190 PCNTL_G(tail)->next = psig;
1191 } else {
1192 PCNTL_G(head) = psig;
1193 }
1194 PCNTL_G(tail) = psig;
1195 PCNTL_G(pending_signals) = 1;
1196 if (PCNTL_G(async_signals)) {
1197 zend_atomic_bool_store_ex(&EG(vm_interrupt), true);
1198 }
1199 }
1200
pcntl_signal_dispatch(void)1201 void pcntl_signal_dispatch(void)
1202 {
1203 zval params[2], *handle, retval;
1204 struct php_pcntl_pending_signal *queue, *next;
1205 sigset_t mask;
1206 sigset_t old_mask;
1207
1208 if(!PCNTL_G(pending_signals)) {
1209 return;
1210 }
1211
1212 /* Mask all signals */
1213 sigfillset(&mask);
1214 sigprocmask(SIG_BLOCK, &mask, &old_mask);
1215
1216 /* Bail if the queue is empty or if we are already playing the queue */
1217 if (!PCNTL_G(head) || PCNTL_G(processing_signal_queue)) {
1218 sigprocmask(SIG_SETMASK, &old_mask, NULL);
1219 return;
1220 }
1221
1222 /* Prevent switching fibers when handling signals */
1223 zend_fiber_switch_block();
1224
1225 /* Prevent reentrant handler calls */
1226 PCNTL_G(processing_signal_queue) = 1;
1227
1228 queue = PCNTL_G(head);
1229 PCNTL_G(head) = NULL; /* simple stores are atomic */
1230
1231 /* Allocate */
1232 while (queue) {
1233 if ((handle = zend_hash_index_find(&PCNTL_G(php_signal_table), queue->signo)) != NULL) {
1234 if (Z_TYPE_P(handle) != IS_LONG) {
1235 ZVAL_NULL(&retval);
1236 ZVAL_LONG(¶ms[0], queue->signo);
1237 #ifdef HAVE_STRUCT_SIGINFO_T
1238 array_init(¶ms[1]);
1239 pcntl_siginfo_to_zval(queue->signo, &queue->siginfo, ¶ms[1]);
1240 #else
1241 ZVAL_NULL(¶ms[1]);
1242 #endif
1243
1244 /* Call php signal handler - Note that we do not report errors, and we ignore the return value */
1245 /* FIXME: this is probably broken when multiple signals are handled in this while loop (retval) */
1246 call_user_function(NULL, NULL, handle, &retval, 2, params);
1247 zval_ptr_dtor(&retval);
1248 #ifdef HAVE_STRUCT_SIGINFO_T
1249 zval_ptr_dtor(¶ms[1]);
1250 #endif
1251 }
1252 }
1253
1254 next = queue->next;
1255 queue->next = PCNTL_G(spares);
1256 PCNTL_G(spares) = queue;
1257 queue = next;
1258 }
1259
1260 PCNTL_G(pending_signals) = 0;
1261
1262 /* Re-enable queue */
1263 PCNTL_G(processing_signal_queue) = 0;
1264
1265 /* Re-enable fiber switching */
1266 zend_fiber_switch_unblock();
1267
1268 /* return signal mask to previous state */
1269 sigprocmask(SIG_SETMASK, &old_mask, NULL);
1270 }
1271
pcntl_signal_dispatch_tick_function(int dummy_int,void * dummy_pointer)1272 static void pcntl_signal_dispatch_tick_function(int dummy_int, void *dummy_pointer)
1273 {
1274 return pcntl_signal_dispatch();
1275 }
1276
1277 /* {{{ Enable/disable asynchronous signal handling and return the old setting. */
PHP_FUNCTION(pcntl_async_signals)1278 PHP_FUNCTION(pcntl_async_signals)
1279 {
1280 bool on, on_is_null = 1;
1281
1282 ZEND_PARSE_PARAMETERS_START(0, 1)
1283 Z_PARAM_OPTIONAL
1284 Z_PARAM_BOOL_OR_NULL(on, on_is_null)
1285 ZEND_PARSE_PARAMETERS_END();
1286
1287 if (on_is_null) {
1288 RETURN_BOOL(PCNTL_G(async_signals));
1289 }
1290
1291 RETVAL_BOOL(PCNTL_G(async_signals));
1292 PCNTL_G(async_signals) = on;
1293 }
1294 /* }}} */
1295
1296 #ifdef HAVE_UNSHARE
1297 /* {{{ disassociate parts of the process execution context */
PHP_FUNCTION(pcntl_unshare)1298 PHP_FUNCTION(pcntl_unshare)
1299 {
1300 zend_long flags;
1301
1302 ZEND_PARSE_PARAMETERS_START(1, 1)
1303 Z_PARAM_LONG(flags)
1304 ZEND_PARSE_PARAMETERS_END();
1305
1306 if (unshare(flags) == -1) {
1307 PCNTL_G(last_error) = errno;
1308 switch (errno) {
1309 #ifdef EINVAL
1310 case EINVAL:
1311 zend_argument_value_error(1, "must be a combination of CLONE_* flags, or at least one flag is unsupported by the kernel");
1312 RETURN_THROWS();
1313 break;
1314 #endif
1315 #ifdef ENOMEM
1316 case ENOMEM:
1317 php_error_docref(NULL, E_WARNING, "Error %d: Insufficient memory for unshare", errno);
1318 break;
1319 #endif
1320 #ifdef EPERM
1321 case EPERM:
1322 php_error_docref(NULL, E_WARNING, "Error %d: No privilege to use these flags", errno);
1323 break;
1324 #endif
1325 #ifdef ENOSPC
1326 case ENOSPC:
1327 php_error_docref(NULL, E_WARNING, "Error %d: Reached the maximum nesting limit for one of the specified namespaces", errno);
1328 break;
1329 #endif
1330 #ifdef EUSERS
1331 case EUSERS:
1332 php_error_docref(NULL, E_WARNING, "Error %d: Reached the maximum nesting limit for the user namespace", errno);
1333 break;
1334 #endif
1335 default:
1336 php_error_docref(NULL, E_WARNING, "Unknown error %d has occurred", errno);
1337 break;
1338 }
1339 RETURN_FALSE;
1340 }
1341
1342 RETURN_TRUE;
1343 }
1344 /* }}} */
1345 #endif
1346
1347 #ifdef HAVE_RFORK
1348 /* {{{ proto bool pcntl_rfork(int flags [, int signal])
1349 More control over the process creation is given over fork/vfork. */
PHP_FUNCTION(pcntl_rfork)1350 PHP_FUNCTION(pcntl_rfork)
1351 {
1352 zend_long flags;
1353 zend_long csignal = 0;
1354 pid_t pid;
1355
1356 ZEND_PARSE_PARAMETERS_START(1, 2)
1357 Z_PARAM_LONG(flags)
1358 Z_PARAM_OPTIONAL
1359 Z_PARAM_LONG(csignal)
1360 ZEND_PARSE_PARAMETERS_END();
1361
1362 /* This is a flag to use with great caution in general, preferably not within PHP */
1363 if ((flags & RFMEM) != 0) {
1364 zend_argument_value_error(1, "must not include RFMEM value, not allowed within this context");
1365 RETURN_THROWS();
1366 }
1367
1368 if ((flags & RFSIGSHARE) != 0) {
1369 zend_argument_value_error(1, "must not include RFSIGSHARE value, not allowed within this context");
1370 RETURN_THROWS();
1371 }
1372
1373 if ((flags & (RFFDG | RFCFDG)) == (RFFDG | RFCFDG)) {
1374 zend_argument_value_error(1, "must not include both RFFDG and RFCFDG, because these flags are mutually exclusive");
1375 RETURN_THROWS();
1376 }
1377
1378 /* A new pid is required */
1379 if (!(flags & (RFPROC))) {
1380 flags |= RFPROC;
1381 }
1382
1383 #ifdef RFTSIGZMB
1384 if ((flags & RFTSIGZMB) != 0) {
1385 flags |= RFTSIGFLAGS(csignal);
1386 }
1387 #endif
1388
1389 pid = rfork(flags);
1390
1391 if (pid == -1) {
1392 PCNTL_G(last_error) = errno;
1393 switch (errno) {
1394 case EAGAIN:
1395 php_error_docref(NULL, E_WARNING, "Maximum process creations limit reached\n");
1396 break;
1397
1398 default:
1399 php_error_docref(NULL, E_WARNING, "Error %d", errno);
1400 }
1401 }
1402
1403 RETURN_LONG((zend_long) pid);
1404 }
1405 #endif
1406 /* }}} */
1407
1408 #ifdef HAVE_FORKX
1409 /* {{{ proto bool pcntl_forkx(int flags)
1410 More elaborated version of fork with the following settings.
1411 FORK_WAITPID: forbid the parent process to wait for multiple pid but one only
1412 FORK_NOSIGCHLD: SIGCHLD signal ignored when the child terminates */
PHP_FUNCTION(pcntl_forkx)1413 PHP_FUNCTION(pcntl_forkx)
1414 {
1415 zend_long flags;
1416 pid_t pid;
1417
1418 ZEND_PARSE_PARAMETERS_START(1, 1)
1419 Z_PARAM_LONG(flags)
1420 ZEND_PARSE_PARAMETERS_END();
1421
1422 if (flags < FORK_NOSIGCHLD || flags > FORK_WAITPID) {
1423 zend_argument_value_error(1, "must be FORK_NOSIGCHLD or FORK_WAITPID");
1424 RETURN_THROWS();
1425 }
1426
1427 pid = forkx(flags);
1428
1429 if (pid == -1) {
1430 PCNTL_G(last_error) = errno;
1431 switch (errno) {
1432 case EAGAIN:
1433 php_error_docref(NULL, E_WARNING, "Maximum process creations limit reached\n");
1434 break;
1435 case EPERM:
1436 php_error_docref(NULL, E_WARNING, "Calling process not having the proper privileges\n");
1437 break;
1438 case ENOMEM:
1439 php_error_docref(NULL, E_WARNING, "No swap space left\n");
1440 break;
1441 default:
1442 php_error_docref(NULL, E_WARNING, "Error %d", errno);
1443 }
1444 }
1445
1446 RETURN_LONG((zend_long) pid);
1447 }
1448 #endif
1449 /* }}} */
1450
1451 #ifdef HAVE_PIDFD_OPEN
1452 // The `pidfd_open` syscall is available since 5.3
1453 // and `setns` since 3.0.
PHP_FUNCTION(pcntl_setns)1454 PHP_FUNCTION(pcntl_setns)
1455 {
1456 zend_long pid, nstype = CLONE_NEWNET;
1457 bool pid_is_null = 1;
1458 int fd, ret;
1459
1460 ZEND_PARSE_PARAMETERS_START(0, 2)
1461 Z_PARAM_OPTIONAL
1462 Z_PARAM_LONG_OR_NULL(pid, pid_is_null)
1463 Z_PARAM_LONG(nstype)
1464 ZEND_PARSE_PARAMETERS_END();
1465
1466 pid = pid_is_null ? getpid() : pid;
1467 fd = syscall(SYS_pidfd_open, pid, 0);
1468 if (errno) {
1469 PCNTL_G(last_error) = errno;
1470 switch (errno) {
1471 case EINVAL:
1472 case ESRCH:
1473 zend_argument_value_error(1, "is not a valid process (" ZEND_LONG_FMT ")", pid);
1474 RETURN_THROWS();
1475
1476 case ENFILE:
1477 php_error_docref(NULL, E_WARNING, "Error %d: File descriptors per-process limit reached", errno);
1478 break;
1479
1480 case ENODEV:
1481 php_error_docref(NULL, E_WARNING, "Error %d: Anonymous inode fs unsupported", errno);
1482 break;
1483
1484 case ENOMEM:
1485 php_error_docref(NULL, E_WARNING, "Error %d: Insufficient memory for pidfd_open", errno);
1486 break;
1487
1488 default:
1489 php_error_docref(NULL, E_WARNING, "Error %d", errno);
1490 }
1491 RETURN_FALSE;
1492 }
1493 ret = setns(fd, (int)nstype);
1494 close(fd);
1495
1496 if (ret == -1) {
1497 PCNTL_G(last_error) = errno;
1498 switch (errno) {
1499 case ESRCH:
1500 zend_argument_value_error(1, "process no longer available (" ZEND_LONG_FMT ")", pid);
1501 RETURN_THROWS();
1502
1503 case EINVAL:
1504 zend_argument_value_error(2, "is an invalid nstype (%d)", nstype);
1505 RETURN_THROWS();
1506
1507 case EPERM:
1508 php_error_docref(NULL, E_WARNING, "Error %d: No required capability for this process", errno);
1509 break;
1510
1511 default:
1512 php_error_docref(NULL, E_WARNING, "Error %d", errno);
1513 }
1514 RETURN_FALSE;
1515 } else {
1516 RETURN_TRUE;
1517 }
1518 }
1519 #endif
1520
1521 #ifdef HAVE_SCHED_SETAFFINITY
PHP_FUNCTION(pcntl_getcpuaffinity)1522 PHP_FUNCTION(pcntl_getcpuaffinity)
1523 {
1524 zend_long pid;
1525 bool pid_is_null = 1;
1526 cpu_set_t mask;
1527
1528 ZEND_PARSE_PARAMETERS_START(0, 1)
1529 Z_PARAM_OPTIONAL
1530 Z_PARAM_LONG_OR_NULL(pid, pid_is_null)
1531 ZEND_PARSE_PARAMETERS_END();
1532
1533 // 0 == getpid in this context, we're just saving a syscall
1534 pid = pid_is_null ? 0 : pid;
1535
1536 CPU_ZERO(&mask);
1537
1538 if (sched_getaffinity(pid, sizeof(mask), &mask) != 0) {
1539 PCNTL_G(last_error) = errno;
1540 switch (errno) {
1541 case ESRCH:
1542 zend_argument_value_error(1, "invalid process (" ZEND_LONG_FMT ")", pid);
1543 RETURN_THROWS();
1544 case EPERM:
1545 php_error_docref(NULL, E_WARNING, "Calling process not having the proper privileges");
1546 break;
1547 case EINVAL:
1548 zend_value_error("invalid cpu affinity mask size");
1549 RETURN_THROWS();
1550 default:
1551 php_error_docref(NULL, E_WARNING, "Error %d", errno);
1552 }
1553
1554 RETURN_FALSE;
1555 }
1556
1557 zend_ulong maxcpus = (zend_ulong)sysconf(_SC_NPROCESSORS_CONF);
1558 array_init(return_value);
1559
1560 for (zend_ulong i = 0; i < maxcpus; i ++) {
1561 if (CPU_ISSET(i, &mask)) {
1562 add_next_index_long(return_value, i);
1563 }
1564 }
1565 }
1566
PHP_FUNCTION(pcntl_setcpuaffinity)1567 PHP_FUNCTION(pcntl_setcpuaffinity)
1568 {
1569 zend_long pid;
1570 bool pid_is_null = 1;
1571 cpu_set_t mask;
1572 zval *hmask = NULL, *ncpu;
1573
1574 ZEND_PARSE_PARAMETERS_START(0, 2)
1575 Z_PARAM_OPTIONAL
1576 Z_PARAM_LONG_OR_NULL(pid, pid_is_null)
1577 Z_PARAM_ARRAY(hmask)
1578 ZEND_PARSE_PARAMETERS_END();
1579
1580 if (!hmask || zend_hash_num_elements(Z_ARRVAL_P(hmask)) == 0) {
1581 zend_argument_value_error(2, "must not be empty");
1582 RETURN_THROWS();
1583 }
1584
1585 // 0 == getpid in this context, we're just saving a syscall
1586 pid = pid_is_null ? 0 : pid;
1587 zend_ulong maxcpus = (zend_ulong)sysconf(_SC_NPROCESSORS_CONF);
1588 CPU_ZERO(&mask);
1589
1590 ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(hmask), ncpu) {
1591 ZVAL_DEREF(ncpu);
1592 zend_long cpu;
1593 if (Z_TYPE_P(ncpu) != IS_LONG) {
1594 if (Z_TYPE_P(ncpu) == IS_STRING) {
1595 zend_ulong tmp;
1596 if (!ZEND_HANDLE_NUMERIC(Z_STR_P(ncpu), tmp)) {
1597 zend_argument_value_error(2, "cpu id invalid value (%s)", ZSTR_VAL(Z_STR_P(ncpu)));
1598 RETURN_THROWS();
1599 }
1600
1601 cpu = (zend_long)tmp;
1602 } else {
1603 zend_string *wcpu = zval_get_string_func(ncpu);
1604 zend_argument_value_error(2, "cpu id invalid type (%s)", ZSTR_VAL(wcpu));
1605 zend_string_release(wcpu);
1606 RETURN_THROWS();
1607 }
1608 } else {
1609 cpu = Z_LVAL_P(ncpu);
1610 }
1611
1612 if (cpu < 0 || cpu >= maxcpus) {
1613 zend_argument_value_error(2, "cpu id must be between 0 and " ZEND_ULONG_FMT " (" ZEND_LONG_FMT ")", maxcpus, cpu);
1614 RETURN_THROWS();
1615 }
1616
1617 if (!CPU_ISSET(cpu, &mask)) {
1618 CPU_SET(cpu, &mask);
1619 }
1620 } ZEND_HASH_FOREACH_END();
1621
1622 if (sched_setaffinity(pid, sizeof(mask), &mask) != 0) {
1623 PCNTL_G(last_error) = errno;
1624 switch (errno) {
1625 case ESRCH:
1626 zend_argument_value_error(1, "invalid process (" ZEND_LONG_FMT ")", pid);
1627 RETURN_THROWS();
1628 case EPERM:
1629 php_error_docref(NULL, E_WARNING, "Calling process not having the proper privileges");
1630 break;
1631 case EINVAL:
1632 zend_argument_value_error(2, "invalid cpu affinity mask size or unmapped cpu id(s)");
1633 RETURN_THROWS();
1634 default:
1635 php_error_docref(NULL, E_WARNING, "Error %d", errno);
1636 }
1637 RETURN_FALSE;
1638 } else {
1639 RETURN_TRUE;
1640 }
1641 }
1642 #endif
1643
1644 #if defined(HAVE_SCHED_GETCPU)
PHP_FUNCTION(pcntl_getcpu)1645 PHP_FUNCTION(pcntl_getcpu)
1646 {
1647 ZEND_PARSE_PARAMETERS_NONE();
1648
1649 RETURN_LONG(sched_getcpu());
1650 }
1651 #endif
1652
1653 #if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP)
qos_zval_to_lval(const zval * qos_obj)1654 static qos_class_t qos_zval_to_lval(const zval *qos_obj)
1655 {
1656 qos_class_t qos_class = QOS_CLASS_DEFAULT;
1657 zend_string *entry = Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(qos_obj)));
1658
1659 if (zend_string_equals_literal(entry, "UserInteractive")) {
1660 qos_class = QOS_CLASS_USER_INTERACTIVE;
1661 } else if (zend_string_equals_literal(entry, "UserInitiated")) {
1662 qos_class = QOS_CLASS_USER_INITIATED;
1663 } else if (zend_string_equals_literal(entry, "Utility")) {
1664 qos_class = QOS_CLASS_UTILITY;
1665 } else if (zend_string_equals_literal(entry, "Background")) {
1666 qos_class = QOS_CLASS_BACKGROUND;
1667 }
1668
1669 return qos_class;
1670 }
1671
qos_lval_to_zval(qos_class_t qos_class)1672 static zend_object *qos_lval_to_zval(qos_class_t qos_class)
1673 {
1674 const char *entryname;
1675 switch (qos_class)
1676 {
1677 case QOS_CLASS_USER_INTERACTIVE:
1678 entryname = "UserInteractive";
1679 break;
1680 case QOS_CLASS_USER_INITIATED:
1681 entryname = "UserInitiated";
1682 break;
1683 case QOS_CLASS_UTILITY:
1684 entryname = "Utility";
1685 break;
1686 case QOS_CLASS_BACKGROUND:
1687 entryname = "Background";
1688 break;
1689 case QOS_CLASS_DEFAULT:
1690 default:
1691 entryname = "Default";
1692 break;
1693 }
1694
1695 return zend_enum_get_case_cstr(QosClass_ce, entryname);
1696 }
1697
PHP_FUNCTION(pcntl_getqos_class)1698 PHP_FUNCTION(pcntl_getqos_class)
1699 {
1700 qos_class_t qos_class;
1701
1702 ZEND_PARSE_PARAMETERS_NONE();
1703
1704 if (UNEXPECTED(pthread_get_qos_class_np(pthread_self(), &qos_class, NULL) != 0))
1705 {
1706 // unlikely unless an external tool set the QOS class with a wrong value
1707 PCNTL_G(last_error) = errno;
1708 zend_throw_error(NULL, "invalid QOS class %u", qos_class);
1709 RETURN_THROWS();
1710 }
1711
1712 RETURN_OBJ_COPY(qos_lval_to_zval(qos_class));
1713 }
1714
PHP_FUNCTION(pcntl_setqos_class)1715 PHP_FUNCTION(pcntl_setqos_class)
1716 {
1717 zval *qos_obj;
1718
1719 ZEND_PARSE_PARAMETERS_START(1, 1)
1720 Z_PARAM_OBJECT_OF_CLASS(qos_obj, QosClass_ce)
1721 ZEND_PARSE_PARAMETERS_END();
1722
1723 qos_class_t qos_class = qos_zval_to_lval(qos_obj);
1724
1725 if (UNEXPECTED(pthread_set_qos_class_self_np((qos_class_t)qos_class, 0) != 0))
1726 {
1727 // unlikely, unless it is a new os issue, as we draw from the specified enum values
1728 PCNTL_G(last_error) = errno;
1729 zend_throw_error(NULL, "pcntl_setqos_class failed");
1730 RETURN_THROWS();
1731 }
1732 }
1733 #endif
1734
pcntl_interrupt_function(zend_execute_data * execute_data)1735 static void pcntl_interrupt_function(zend_execute_data *execute_data)
1736 {
1737 pcntl_signal_dispatch();
1738 if (orig_interrupt_function) {
1739 orig_interrupt_function(execute_data);
1740 }
1741 }
1742