xref: /php-src/ext/pcntl/pcntl.c (revision 23640542)
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(&params[0], queue->signo);
1237 #ifdef HAVE_STRUCT_SIGINFO_T
1238 				array_init(&params[1]);
1239 				pcntl_siginfo_to_zval(queue->signo, &queue->siginfo, &params[1]);
1240 #else
1241 				ZVAL_NULL(&params[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(&params[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