xref: /PHP-7.4/ext/standard/proc_open.c (revision e1de11d4)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) The PHP Group                                          |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Author: Wez Furlong <wez@thebrainroom.com>                           |
16    +----------------------------------------------------------------------+
17  */
18 
19 #if 0 && (defined(__linux__) || defined(sun) || defined(__IRIX__))
20 # define _BSD_SOURCE 		/* linux wants this when XOPEN mode is on */
21 # define _BSD_COMPAT		/* irix: uint32_t */
22 # define _XOPEN_SOURCE 500  /* turn on Unix98 */
23 # define __EXTENSIONS__	1	/* Solaris: uint32_t */
24 #endif
25 
26 #include "php.h"
27 #include <stdio.h>
28 #include <ctype.h>
29 #include "php_string.h"
30 #include "ext/standard/head.h"
31 #include "ext/standard/basic_functions.h"
32 #include "ext/standard/file.h"
33 #include "exec.h"
34 #include "php_globals.h"
35 #include "SAPI.h"
36 #include "main/php_network.h"
37 #include "zend_smart_string.h"
38 
39 #if HAVE_SYS_WAIT_H
40 #include <sys/wait.h>
41 #endif
42 #include <signal.h>
43 
44 #if HAVE_SYS_STAT_H
45 #include <sys/stat.h>
46 #endif
47 #if HAVE_FCNTL_H
48 #include <fcntl.h>
49 #endif
50 
51 /* This symbol is defined in ext/standard/config.m4.
52  * Essentially, it is set if you HAVE_FORK || PHP_WIN32
53  * Other platforms may modify that configure check and add suitable #ifdefs
54  * around the alternate code.
55  * */
56 #ifdef PHP_CAN_SUPPORT_PROC_OPEN
57 
58 #if 0 && HAVE_PTSNAME && HAVE_GRANTPT && HAVE_UNLOCKPT && HAVE_SYS_IOCTL_H && HAVE_TERMIOS_H
59 # include <sys/ioctl.h>
60 # include <termios.h>
61 # define PHP_CAN_DO_PTS	1
62 #endif
63 
64 #include "proc_open.h"
65 
66 static int le_proc_open;
67 
68 /* {{{ _php_array_to_envp */
_php_array_to_envp(zval * environment,int is_persistent)69 static php_process_env_t _php_array_to_envp(zval *environment, int is_persistent)
70 {
71 	zval *element;
72 	php_process_env_t env;
73 	zend_string *key, *str;
74 #ifndef PHP_WIN32
75 	char **ep;
76 #endif
77 	char *p;
78 	size_t cnt, sizeenv = 0;
79 	HashTable *env_hash;
80 
81 	memset(&env, 0, sizeof(env));
82 
83 	if (!environment) {
84 		return env;
85 	}
86 
87 	cnt = zend_hash_num_elements(Z_ARRVAL_P(environment));
88 
89 	if (cnt < 1) {
90 #ifndef PHP_WIN32
91 		env.envarray = (char **) pecalloc(1, sizeof(char *), is_persistent);
92 #endif
93 		env.envp = (char *) pecalloc(4, 1, is_persistent);
94 		return env;
95 	}
96 
97 	ALLOC_HASHTABLE(env_hash);
98 	zend_hash_init(env_hash, cnt, NULL, NULL, 0);
99 
100 	/* first, we have to get the size of all the elements in the hash */
101 	ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(environment), key, element) {
102 		str = zval_get_string(element);
103 
104 		if (ZSTR_LEN(str) == 0) {
105 			zend_string_release_ex(str, 0);
106 			continue;
107 		}
108 
109 		sizeenv += ZSTR_LEN(str) + 1;
110 
111 		if (key && ZSTR_LEN(key)) {
112 			sizeenv += ZSTR_LEN(key) + 1;
113 			zend_hash_add_ptr(env_hash, key, str);
114 		} else {
115 			zend_hash_next_index_insert_ptr(env_hash, str);
116 		}
117 	} ZEND_HASH_FOREACH_END();
118 
119 #ifndef PHP_WIN32
120 	ep = env.envarray = (char **) pecalloc(cnt + 1, sizeof(char *), is_persistent);
121 #endif
122 	p = env.envp = (char *) pecalloc(sizeenv + 4, 1, is_persistent);
123 
124 	ZEND_HASH_FOREACH_STR_KEY_PTR(env_hash, key, str) {
125 #ifndef PHP_WIN32
126 		*ep = p;
127 		++ep;
128 #endif
129 
130 		if (key) {
131 			memcpy(p, ZSTR_VAL(key), ZSTR_LEN(key));
132 			p += ZSTR_LEN(key);
133 			*p++ = '=';
134 		}
135 
136 		memcpy(p, ZSTR_VAL(str), ZSTR_LEN(str));
137 		p += ZSTR_LEN(str);
138 		*p++ = '\0';
139 		zend_string_release_ex(str, 0);
140 	} ZEND_HASH_FOREACH_END();
141 
142 	assert((uint32_t)(p - env.envp) <= sizeenv);
143 
144 	zend_hash_destroy(env_hash);
145 	FREE_HASHTABLE(env_hash);
146 
147 	return env;
148 }
149 /* }}} */
150 
151 /* {{{ _php_free_envp */
_php_free_envp(php_process_env_t env,int is_persistent)152 static void _php_free_envp(php_process_env_t env, int is_persistent)
153 {
154 #ifndef PHP_WIN32
155 	if (env.envarray) {
156 		pefree(env.envarray, is_persistent);
157 	}
158 #endif
159 	if (env.envp) {
160 		pefree(env.envp, is_persistent);
161 	}
162 }
163 /* }}} */
164 
165 /* {{{ proc_open_rsrc_dtor */
proc_open_rsrc_dtor(zend_resource * rsrc)166 static void proc_open_rsrc_dtor(zend_resource *rsrc)
167 {
168 	struct php_process_handle *proc = (struct php_process_handle*)rsrc->ptr;
169 	int i;
170 #ifdef PHP_WIN32
171 	DWORD wstatus;
172 #elif HAVE_SYS_WAIT_H
173 	int wstatus;
174 	int waitpid_options = 0;
175 	pid_t wait_pid;
176 #endif
177 
178 	/* Close all handles to avoid a deadlock */
179 	for (i = 0; i < proc->npipes; i++) {
180 		if (proc->pipes[i] != 0) {
181 			GC_DELREF(proc->pipes[i]);
182 			zend_list_close(proc->pipes[i]);
183 			proc->pipes[i] = 0;
184 		}
185 	}
186 
187 #ifdef PHP_WIN32
188 	if (FG(pclose_wait)) {
189 		WaitForSingleObject(proc->childHandle, INFINITE);
190 	}
191 	GetExitCodeProcess(proc->childHandle, &wstatus);
192 	if (wstatus == STILL_ACTIVE) {
193 		FG(pclose_ret) = -1;
194 	} else {
195 		FG(pclose_ret) = wstatus;
196 	}
197 	CloseHandle(proc->childHandle);
198 
199 #elif HAVE_SYS_WAIT_H
200 
201 	if (!FG(pclose_wait)) {
202 		waitpid_options = WNOHANG;
203 	}
204 	do {
205 		wait_pid = waitpid(proc->child, &wstatus, waitpid_options);
206 	} while (wait_pid == -1 && errno == EINTR);
207 
208 	if (wait_pid <= 0) {
209 		FG(pclose_ret) = -1;
210 	} else {
211 		if (WIFEXITED(wstatus))
212 			wstatus = WEXITSTATUS(wstatus);
213 		FG(pclose_ret) = wstatus;
214 	}
215 
216 #else
217 	FG(pclose_ret) = -1;
218 #endif
219 	_php_free_envp(proc->env, proc->is_persistent);
220 	pefree(proc->pipes, proc->is_persistent);
221 	pefree(proc->command, proc->is_persistent);
222 	pefree(proc, proc->is_persistent);
223 
224 }
225 /* }}} */
226 
227 /* {{{ PHP_MINIT_FUNCTION(proc_open) */
PHP_MINIT_FUNCTION(proc_open)228 PHP_MINIT_FUNCTION(proc_open)
229 {
230 	le_proc_open = zend_register_list_destructors_ex(proc_open_rsrc_dtor, NULL, "process", module_number);
231 	return SUCCESS;
232 }
233 /* }}} */
234 
235 /* {{{ proto bool proc_terminate(resource process [, int signal])
236    kill a process opened by proc_open */
PHP_FUNCTION(proc_terminate)237 PHP_FUNCTION(proc_terminate)
238 {
239 	zval *zproc;
240 	struct php_process_handle *proc;
241 	zend_long sig_no = SIGTERM;
242 
243 	ZEND_PARSE_PARAMETERS_START(1, 2)
244 		Z_PARAM_RESOURCE(zproc)
245 		Z_PARAM_OPTIONAL
246 		Z_PARAM_LONG(sig_no)
247 	ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
248 
249 	if ((proc = (struct php_process_handle *)zend_fetch_resource(Z_RES_P(zproc), "process", le_proc_open)) == NULL) {
250 		RETURN_FALSE;
251 	}
252 
253 #ifdef PHP_WIN32
254 	if (TerminateProcess(proc->childHandle, 255)) {
255 		RETURN_TRUE;
256 	} else {
257 		RETURN_FALSE;
258 	}
259 #else
260 	if (kill(proc->child, sig_no) == 0) {
261 		RETURN_TRUE;
262 	} else {
263 		RETURN_FALSE;
264 	}
265 #endif
266 }
267 /* }}} */
268 
269 /* {{{ proto int proc_close(resource process)
270    close a process opened by proc_open */
PHP_FUNCTION(proc_close)271 PHP_FUNCTION(proc_close)
272 {
273 	zval *zproc;
274 	struct php_process_handle *proc;
275 
276 	ZEND_PARSE_PARAMETERS_START(1, 1)
277 		Z_PARAM_RESOURCE(zproc)
278 	ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
279 
280 	if ((proc = (struct php_process_handle *)zend_fetch_resource(Z_RES_P(zproc), "process", le_proc_open)) == NULL) {
281 		RETURN_FALSE;
282 	}
283 
284 	FG(pclose_wait) = 1;
285 	zend_list_close(Z_RES_P(zproc));
286 	FG(pclose_wait) = 0;
287 	RETURN_LONG(FG(pclose_ret));
288 }
289 /* }}} */
290 
291 /* {{{ proto array proc_get_status(resource process)
292    get information about a process opened by proc_open */
PHP_FUNCTION(proc_get_status)293 PHP_FUNCTION(proc_get_status)
294 {
295 	zval *zproc;
296 	struct php_process_handle *proc;
297 #ifdef PHP_WIN32
298 	DWORD wstatus;
299 #elif HAVE_SYS_WAIT_H
300 	int wstatus;
301 	pid_t wait_pid;
302 #endif
303 	int running = 1, signaled = 0, stopped = 0;
304 	int exitcode = -1, termsig = 0, stopsig = 0;
305 
306 	ZEND_PARSE_PARAMETERS_START(1, 1)
307 		Z_PARAM_RESOURCE(zproc)
308 	ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
309 
310 	if ((proc = (struct php_process_handle *)zend_fetch_resource(Z_RES_P(zproc), "process", le_proc_open)) == NULL) {
311 		RETURN_FALSE;
312 	}
313 
314 	array_init(return_value);
315 
316 	add_assoc_string(return_value, "command", proc->command);
317 	add_assoc_long(return_value, "pid", (zend_long) proc->child);
318 
319 #ifdef PHP_WIN32
320 
321 	GetExitCodeProcess(proc->childHandle, &wstatus);
322 
323 	running = wstatus == STILL_ACTIVE;
324 	exitcode = running ? -1 : wstatus;
325 
326 #elif HAVE_SYS_WAIT_H
327 
328 	errno = 0;
329 	wait_pid = waitpid(proc->child, &wstatus, WNOHANG|WUNTRACED);
330 
331 	if (wait_pid == proc->child) {
332 		if (WIFEXITED(wstatus)) {
333 			running = 0;
334 			exitcode = WEXITSTATUS(wstatus);
335 		}
336 		if (WIFSIGNALED(wstatus)) {
337 			running = 0;
338 			signaled = 1;
339 
340 			termsig = WTERMSIG(wstatus);
341 		}
342 		if (WIFSTOPPED(wstatus)) {
343 			stopped = 1;
344 			stopsig = WSTOPSIG(wstatus);
345 		}
346 	} else if (wait_pid == -1) {
347 		running = 0;
348 	}
349 #endif
350 
351 	add_assoc_bool(return_value, "running", running);
352 	add_assoc_bool(return_value, "signaled", signaled);
353 	add_assoc_bool(return_value, "stopped", stopped);
354 	add_assoc_long(return_value, "exitcode", exitcode);
355 	add_assoc_long(return_value, "termsig", termsig);
356 	add_assoc_long(return_value, "stopsig", stopsig);
357 }
358 /* }}} */
359 
360 /* {{{ handy definitions for portability/readability */
361 #ifdef PHP_WIN32
362 # define pipe(pair)		(CreatePipe(&pair[0], &pair[1], &security, 0) ? 0 : -1)
363 
364 # define COMSPEC_NT	"cmd.exe"
365 
dup_handle(HANDLE src,BOOL inherit,BOOL closeorig)366 static inline HANDLE dup_handle(HANDLE src, BOOL inherit, BOOL closeorig)
367 {
368 	HANDLE copy, self = GetCurrentProcess();
369 
370 	if (!DuplicateHandle(self, src, self, &copy, 0, inherit, DUPLICATE_SAME_ACCESS |
371 				(closeorig ? DUPLICATE_CLOSE_SOURCE : 0)))
372 		return NULL;
373 	return copy;
374 }
375 
dup_fd_as_handle(int fd)376 static inline HANDLE dup_fd_as_handle(int fd)
377 {
378 	return dup_handle((HANDLE)_get_osfhandle(fd), TRUE, FALSE);
379 }
380 
381 # define close_descriptor(fd)	CloseHandle(fd)
382 #else
383 # define close_descriptor(fd)	close(fd)
384 #endif
385 
386 #define DESC_PIPE		1
387 #define DESC_FILE		2
388 #define DESC_REDIRECT	3
389 #define DESC_PARENT_MODE_WRITE	8
390 
391 struct php_proc_open_descriptor_item {
392 	int index; 							/* desired fd number in child process */
393 	php_file_descriptor_t parentend, childend;	/* fds for pipes in parent/child */
394 	int mode;							/* mode for proc_open code */
395 	int mode_flags;						/* mode flags for opening fds */
396 };
397 /* }}} */
398 
get_valid_arg_string(zval * zv,int elem_num)399 static zend_string *get_valid_arg_string(zval *zv, int elem_num) {
400 	zend_string *str = zval_get_string(zv);
401 	if (!str) {
402 		return NULL;
403 	}
404 
405 	if (strlen(ZSTR_VAL(str)) != ZSTR_LEN(str)) {
406 		php_error_docref(NULL, E_WARNING,
407 			"Command array element %d contains a null byte", elem_num);
408 		zend_string_release(str);
409 		return NULL;
410 	}
411 
412 	return str;
413 }
414 
415 #ifdef PHP_WIN32
append_backslashes(smart_string * str,size_t num_bs)416 static void append_backslashes(smart_string *str, size_t num_bs) {
417 	size_t i;
418 	for (i = 0; i < num_bs; i++) {
419 		smart_string_appendc(str, '\\');
420 	}
421 }
422 
423 /* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments */
append_win_escaped_arg(smart_string * str,char * arg)424 static void append_win_escaped_arg(smart_string *str, char *arg) {
425 	char c;
426 	size_t num_bs = 0;
427 	smart_string_appendc(str, '"');
428 	while ((c = *arg)) {
429 		if (c == '\\') {
430 			num_bs++;
431 		} else {
432 			if (c == '"') {
433 				/* Backslashes before " need to be doubled. */
434 				num_bs = num_bs * 2 + 1;
435 			}
436 			append_backslashes(str, num_bs);
437 			smart_string_appendc(str, c);
438 			num_bs = 0;
439 		}
440 		arg++;
441 	}
442 	append_backslashes(str, num_bs * 2);
443 	smart_string_appendc(str, '"');
444 }
445 
create_win_command_from_args(HashTable * args)446 static char *create_win_command_from_args(HashTable *args) {
447 	smart_string str = {0};
448 	zval *arg_zv;
449 	zend_bool is_prog_name = 1;
450 	int elem_num = 0;
451 
452 	ZEND_HASH_FOREACH_VAL(args, arg_zv) {
453 		zend_string *arg_str = get_valid_arg_string(arg_zv, ++elem_num);
454 		if (!arg_str) {
455 			smart_string_free(&str);
456 			return NULL;
457 		}
458 
459 		if (!is_prog_name) {
460 			smart_string_appendc(&str, ' ');
461 		}
462 
463 		append_win_escaped_arg(&str, ZSTR_VAL(arg_str));
464 
465 		is_prog_name = 0;
466 		zend_string_release(arg_str);
467 	} ZEND_HASH_FOREACH_END();
468 	smart_string_0(&str);
469 	return str.c;
470 }
471 #endif
472 
473 /* {{{ proto resource proc_open(string|array command, array descriptorspec, array &pipes [, string cwd [, array env [, array other_options]]])
474    Run a process with more control over it's file descriptors */
PHP_FUNCTION(proc_open)475 PHP_FUNCTION(proc_open)
476 {
477 	zval *command_zv;
478 	char *command = NULL, *cwd = NULL;
479 	size_t cwd_len = 0;
480 	zval *descriptorspec;
481 	zval *pipes;
482 	zval *environment = NULL;
483 	zval *other_options = NULL;
484 	php_process_env_t env;
485 	int ndesc = 0;
486 	int i;
487 	zval *descitem = NULL;
488 	zend_string *str_index;
489 	zend_ulong nindex;
490 	struct php_proc_open_descriptor_item *descriptors = NULL;
491 	int ndescriptors_array;
492 #ifdef PHP_WIN32
493 	PROCESS_INFORMATION pi;
494 	HANDLE childHandle;
495 	STARTUPINFOW si;
496 	BOOL newprocok;
497 	SECURITY_ATTRIBUTES security;
498 	DWORD dwCreateFlags = 0;
499 	UINT old_error_mode;
500 	char cur_cwd[MAXPATHLEN];
501 	wchar_t *cmdw = NULL, *cwdw = NULL, *envpw = NULL;
502 	size_t tmp_len;
503 	int suppress_errors = 0;
504 	int bypass_shell = 0;
505 	int blocking_pipes = 0;
506 	int create_process_group = 0;
507 	int create_new_console = 0;
508 #else
509 	char **argv = NULL;
510 #endif
511 	php_process_id_t child;
512 	struct php_process_handle *proc;
513 	int is_persistent = 0; /* TODO: ensure that persistent procs will work */
514 #if PHP_CAN_DO_PTS
515 	php_file_descriptor_t dev_ptmx = -1;	/* master */
516 	php_file_descriptor_t slave_pty = -1;
517 #endif
518 
519 	ZEND_PARSE_PARAMETERS_START(3, 6)
520 		Z_PARAM_ZVAL(command_zv)
521 		Z_PARAM_ARRAY(descriptorspec)
522 		Z_PARAM_ZVAL(pipes)
523 		Z_PARAM_OPTIONAL
524 		Z_PARAM_STRING_EX(cwd, cwd_len, 1, 0)
525 		Z_PARAM_ARRAY_EX(environment, 1, 0)
526 		Z_PARAM_ARRAY_EX(other_options, 1, 0)
527 	ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
528 
529 	memset(&env, 0, sizeof(env));
530 
531 	if (Z_TYPE_P(command_zv) == IS_ARRAY) {
532 		zval *arg_zv;
533 		uint32_t num_elems = zend_hash_num_elements(Z_ARRVAL_P(command_zv));
534 		if (num_elems == 0) {
535 			php_error_docref(NULL, E_WARNING, "Command array must have at least one element");
536 			RETURN_FALSE;
537 		}
538 
539 #ifdef PHP_WIN32
540 		bypass_shell = 1;
541 		command = create_win_command_from_args(Z_ARRVAL_P(command_zv));
542 		if (!command) {
543 			RETURN_FALSE;
544 		}
545 #else
546 		argv = safe_emalloc(sizeof(char *), num_elems + 1, 0);
547 		i = 0;
548 		ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(command_zv), arg_zv) {
549 			zend_string *arg_str = get_valid_arg_string(arg_zv, i + 1);
550 			if (!arg_str) {
551 				argv[i] = NULL;
552 				goto exit_fail;
553 			}
554 
555 			if (i == 0) {
556 				command = pestrdup(ZSTR_VAL(arg_str), is_persistent);
557 			}
558 
559 			argv[i++] = estrdup(ZSTR_VAL(arg_str));
560 			zend_string_release(arg_str);
561 		} ZEND_HASH_FOREACH_END();
562 		argv[i] = NULL;
563 
564 		/* As the array is non-empty, we should have found a command. */
565 		ZEND_ASSERT(command);
566 #endif
567 	} else {
568 		convert_to_string(command_zv);
569 		command = pestrdup(Z_STRVAL_P(command_zv), is_persistent);
570 	}
571 
572 #ifdef PHP_WIN32
573 	if (other_options) {
574 		zval *item = zend_hash_str_find(Z_ARRVAL_P(other_options), "suppress_errors", sizeof("suppress_errors") - 1);
575 		if (item != NULL) {
576 			if (Z_TYPE_P(item) == IS_TRUE || ((Z_TYPE_P(item) == IS_LONG) && Z_LVAL_P(item))) {
577 				suppress_errors = 1;
578 			}
579 		}
580 
581 		/* TODO Deprecate in favor of array command? */
582 		item = zend_hash_str_find(Z_ARRVAL_P(other_options), "bypass_shell", sizeof("bypass_shell") - 1);
583 		if (item != NULL) {
584 			if (Z_TYPE_P(item) == IS_TRUE || ((Z_TYPE_P(item) == IS_LONG) && Z_LVAL_P(item))) {
585 				bypass_shell = 1;
586 			}
587 		}
588 
589 		item = zend_hash_str_find(Z_ARRVAL_P(other_options), "blocking_pipes", sizeof("blocking_pipes") - 1);
590 		if (item != NULL) {
591 			if (Z_TYPE_P(item) == IS_TRUE || ((Z_TYPE_P(item) == IS_LONG) && Z_LVAL_P(item))) {
592 				blocking_pipes = 1;
593 			}
594 		}
595 
596 		item = zend_hash_str_find(Z_ARRVAL_P(other_options), "create_process_group", sizeof("create_process_group") - 1);
597 		if (item != NULL) {
598 			if (Z_TYPE_P(item) == IS_TRUE || ((Z_TYPE_P(item) == IS_LONG) && Z_LVAL_P(item))) {
599 				create_process_group = 1;
600 			}
601 		}
602 
603 		item = zend_hash_str_find(Z_ARRVAL_P(other_options), "create_new_console", sizeof("create_new_console") - 1);
604 		if (item != NULL) {
605 			if (Z_TYPE_P(item) == IS_TRUE || ((Z_TYPE_P(item) == IS_LONG) && Z_LVAL_P(item))) {
606 				create_new_console = 1;
607 			}
608 		}
609 	}
610 #endif
611 
612 	if (environment) {
613 		env = _php_array_to_envp(environment, is_persistent);
614 	}
615 
616 	ndescriptors_array = zend_hash_num_elements(Z_ARRVAL_P(descriptorspec));
617 
618 	descriptors = safe_emalloc(sizeof(struct php_proc_open_descriptor_item), ndescriptors_array, 0);
619 
620 	memset(descriptors, 0, sizeof(struct php_proc_open_descriptor_item) * ndescriptors_array);
621 
622 #ifdef PHP_WIN32
623 	/* we use this to allow the child to inherit handles */
624 	memset(&security, 0, sizeof(security));
625 	security.nLength = sizeof(security);
626 	security.bInheritHandle = TRUE;
627 	security.lpSecurityDescriptor = NULL;
628 #endif
629 
630 	/* walk the descriptor spec and set up files/pipes */
631 	ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(descriptorspec), nindex, str_index, descitem) {
632 		zval *ztype;
633 
634 		if (str_index) {
635 			php_error_docref(NULL, E_WARNING, "descriptor spec must be an integer indexed array");
636 			goto exit_fail;
637 		}
638 
639 		descriptors[ndesc].index = (int)nindex;
640 
641 		if (Z_TYPE_P(descitem) == IS_RESOURCE) {
642 			/* should be a stream - try and dup the descriptor */
643 			php_stream *stream;
644 			php_socket_t fd;
645 
646 			php_stream_from_zval(stream, descitem);
647 
648 			if (FAILURE == php_stream_cast(stream, PHP_STREAM_AS_FD, (void **)&fd, REPORT_ERRORS)) {
649 				goto exit_fail;
650 			}
651 
652 #ifdef PHP_WIN32
653 			descriptors[ndesc].childend = dup_fd_as_handle((int)fd);
654 			if (descriptors[ndesc].childend == NULL) {
655 				php_error_docref(NULL, E_WARNING, "unable to dup File-Handle for descriptor %d", nindex);
656 				goto exit_fail;
657 			}
658 #else
659 			descriptors[ndesc].childend = dup(fd);
660 			if (descriptors[ndesc].childend < 0) {
661 				php_error_docref(NULL, E_WARNING, "unable to dup File-Handle for descriptor " ZEND_ULONG_FMT " - %s", nindex, strerror(errno));
662 				goto exit_fail;
663 			}
664 #endif
665 			descriptors[ndesc].mode = DESC_FILE;
666 
667 		} else if (Z_TYPE_P(descitem) != IS_ARRAY) {
668 			php_error_docref(NULL, E_WARNING, "Descriptor item must be either an array or a File-Handle");
669 			goto exit_fail;
670 		} else {
671 
672 			if ((ztype = zend_hash_index_find(Z_ARRVAL_P(descitem), 0)) != NULL) {
673 				if (!try_convert_to_string(ztype)) {
674 					goto exit_fail;
675 				}
676 			} else {
677 				php_error_docref(NULL, E_WARNING, "Missing handle qualifier in array");
678 				goto exit_fail;
679 			}
680 
681 			if (strcmp(Z_STRVAL_P(ztype), "pipe") == 0) {
682 				php_file_descriptor_t newpipe[2];
683 				zval *zmode;
684 
685 				if ((zmode = zend_hash_index_find(Z_ARRVAL_P(descitem), 1)) != NULL) {
686 					if (!try_convert_to_string(zmode)) {
687 						goto exit_fail;
688 					}
689 				} else {
690 					php_error_docref(NULL, E_WARNING, "Missing mode parameter for 'pipe'");
691 					goto exit_fail;
692 				}
693 
694 				descriptors[ndesc].mode = DESC_PIPE;
695 
696 				if (0 != pipe(newpipe)) {
697 					php_error_docref(NULL, E_WARNING, "unable to create pipe %s", strerror(errno));
698 					goto exit_fail;
699 				}
700 
701 				if (strncmp(Z_STRVAL_P(zmode), "w", 1) != 0) {
702 					descriptors[ndesc].parentend = newpipe[1];
703 					descriptors[ndesc].childend = newpipe[0];
704 					descriptors[ndesc].mode |= DESC_PARENT_MODE_WRITE;
705 				} else {
706 					descriptors[ndesc].parentend = newpipe[0];
707 					descriptors[ndesc].childend = newpipe[1];
708 				}
709 #ifdef PHP_WIN32
710 				/* don't let the child inherit the parent side of the pipe */
711 				descriptors[ndesc].parentend = dup_handle(descriptors[ndesc].parentend, FALSE, TRUE);
712 #endif
713 				descriptors[ndesc].mode_flags = descriptors[ndesc].mode & DESC_PARENT_MODE_WRITE ? O_WRONLY : O_RDONLY;
714 #ifdef PHP_WIN32
715 				if (Z_STRLEN_P(zmode) >= 2 && Z_STRVAL_P(zmode)[1] == 'b')
716 					descriptors[ndesc].mode_flags |= O_BINARY;
717 #endif
718 
719 			} else if (strcmp(Z_STRVAL_P(ztype), "file") == 0) {
720 				zval *zfile, *zmode;
721 				php_socket_t fd;
722 				php_stream *stream;
723 
724 				descriptors[ndesc].mode = DESC_FILE;
725 
726 				if ((zfile = zend_hash_index_find(Z_ARRVAL_P(descitem), 1)) != NULL) {
727 					if (!try_convert_to_string(zfile)) {
728 						goto exit_fail;
729 					}
730 				} else {
731 					php_error_docref(NULL, E_WARNING, "Missing file name parameter for 'file'");
732 					goto exit_fail;
733 				}
734 
735 				if ((zmode = zend_hash_index_find(Z_ARRVAL_P(descitem), 2)) != NULL) {
736 					if (!try_convert_to_string(zmode)) {
737 						goto exit_fail;
738 					}
739 				} else {
740 					php_error_docref(NULL, E_WARNING, "Missing mode parameter for 'file'");
741 					goto exit_fail;
742 				}
743 
744 				/* try a wrapper */
745 				stream = php_stream_open_wrapper(Z_STRVAL_P(zfile), Z_STRVAL_P(zmode),
746 						REPORT_ERRORS|STREAM_WILL_CAST, NULL);
747 
748 				/* force into an fd */
749 				if (stream == NULL || FAILURE == php_stream_cast(stream,
750 							PHP_STREAM_CAST_RELEASE|PHP_STREAM_AS_FD,
751 							(void **)&fd, REPORT_ERRORS)) {
752 					goto exit_fail;
753 				}
754 
755 #ifdef PHP_WIN32
756 				descriptors[ndesc].childend = dup_fd_as_handle((int)fd);
757 				_close((int)fd);
758 
759 				/* simulate the append mode by fseeking to the end of the file
760 				this introduces a potential race-condition, but it is the best we can do, though */
761 				if (strchr(Z_STRVAL_P(zmode), 'a')) {
762 					SetFilePointer(descriptors[ndesc].childend, 0, NULL, FILE_END);
763 				}
764 #else
765 				descriptors[ndesc].childend = fd;
766 #endif
767 			} else if (strcmp(Z_STRVAL_P(ztype), "redirect") == 0) {
768 				zval *ztarget = zend_hash_index_find_deref(Z_ARRVAL_P(descitem), 1);
769 				struct php_proc_open_descriptor_item *target = NULL;
770 				php_file_descriptor_t childend;
771 
772 				if (!ztarget) {
773 					php_error_docref(NULL, E_WARNING, "Missing redirection target");
774 					goto exit_fail;
775 				}
776 				if (Z_TYPE_P(ztarget) != IS_LONG) {
777 					php_error_docref(NULL, E_WARNING, "Redirection target must be an integer");
778 					goto exit_fail;
779 				}
780 
781 				for (i = 0; i < ndesc; i++) {
782 					if (descriptors[i].index == Z_LVAL_P(ztarget)) {
783 						target = &descriptors[i];
784 						break;
785 					}
786 				}
787 				if (target) {
788 					childend = target->childend;
789 				} else {
790 					if (Z_LVAL_P(ztarget) < 0 || Z_LVAL_P(ztarget) > 2) {
791 						php_error_docref(NULL, E_WARNING,
792 							"Redirection target " ZEND_LONG_FMT " not found", Z_LVAL_P(ztarget));
793 						goto exit_fail;
794 					}
795 
796 					/* Support referring to a stdin/stdout/stderr pipe adopted from the parent,
797 					 * which happens whenever an explicit override is not provided. */
798 #ifndef PHP_WIN32
799 					childend = Z_LVAL_P(ztarget);
800 #else
801 					switch (Z_LVAL_P(ztarget)) {
802 						case 0: childend = GetStdHandle(STD_INPUT_HANDLE); break;
803 						case 1: childend = GetStdHandle(STD_OUTPUT_HANDLE); break;
804 						case 2: childend = GetStdHandle(STD_ERROR_HANDLE); break;
805 						EMPTY_SWITCH_DEFAULT_CASE()
806 					}
807 #endif
808 				}
809 
810 #ifdef PHP_WIN32
811 				descriptors[ndesc].childend = dup_handle(childend, TRUE, FALSE);
812 				if (descriptors[ndesc].childend == NULL) {
813 					php_error_docref(NULL, E_WARNING,
814 						"Failed to dup() for descriptor " ZEND_LONG_FMT, nindex);
815 					goto exit_fail;
816 				}
817 #else
818 				descriptors[ndesc].childend = dup(childend);
819 				if (descriptors[ndesc].childend < 0) {
820 					php_error_docref(NULL, E_WARNING,
821 						"Failed to dup() for descriptor " ZEND_LONG_FMT " - %s",
822 						nindex, strerror(errno));
823 					goto exit_fail;
824 				}
825 #endif
826 				descriptors[ndesc].mode = DESC_REDIRECT;
827 			} else if (strcmp(Z_STRVAL_P(ztype), "null") == 0) {
828 #ifndef PHP_WIN32
829 				descriptors[ndesc].childend = open("/dev/null", O_RDWR);
830 				if (descriptors[ndesc].childend < 0) {
831 					php_error_docref(NULL, E_WARNING,
832 						"Failed to open /dev/null - %s", strerror(errno));
833 					goto exit_fail;
834 				}
835 #else
836 				descriptors[ndesc].childend = CreateFileA(
837 					"nul", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
838 					NULL, OPEN_EXISTING, 0, NULL);
839 				if (descriptors[ndesc].childend == NULL) {
840 					php_error_docref(NULL, E_WARNING, "Failed to open nul");
841 					goto exit_fail;
842 				}
843 #endif
844 				descriptors[ndesc].mode = DESC_FILE;
845 			} else if (strcmp(Z_STRVAL_P(ztype), "pty") == 0) {
846 #if PHP_CAN_DO_PTS
847 				if (dev_ptmx == -1) {
848 					/* open things up */
849 					dev_ptmx = open("/dev/ptmx", O_RDWR);
850 					if (dev_ptmx == -1) {
851 						php_error_docref(NULL, E_WARNING, "failed to open /dev/ptmx, errno %d", errno);
852 						goto exit_fail;
853 					}
854 					grantpt(dev_ptmx);
855 					unlockpt(dev_ptmx);
856 					slave_pty = open(ptsname(dev_ptmx), O_RDWR);
857 
858 					if (slave_pty == -1) {
859 						php_error_docref(NULL, E_WARNING, "failed to open slave pty, errno %d", errno);
860 						goto exit_fail;
861 					}
862 				}
863 				descriptors[ndesc].mode = DESC_PIPE;
864 				descriptors[ndesc].childend = dup(slave_pty);
865 				descriptors[ndesc].parentend = dup(dev_ptmx);
866 				descriptors[ndesc].mode_flags = O_RDWR;
867 #else
868 				php_error_docref(NULL, E_WARNING, "pty pseudo terminal not supported on this system");
869 				goto exit_fail;
870 #endif
871 			} else {
872 				php_error_docref(NULL, E_WARNING, "%s is not a valid descriptor spec/mode", Z_STRVAL_P(ztype));
873 				goto exit_fail;
874 			}
875 		}
876 		ndesc++;
877 	} ZEND_HASH_FOREACH_END();
878 
879 #ifdef PHP_WIN32
880 	if (cwd == NULL) {
881 		char *getcwd_result;
882 		getcwd_result = VCWD_GETCWD(cur_cwd, MAXPATHLEN);
883 		if (!getcwd_result) {
884 			php_error_docref(NULL, E_WARNING, "Cannot get current directory");
885 			goto exit_fail;
886 		}
887 		cwd = cur_cwd;
888 	}
889 	cwdw = php_win32_cp_any_to_w(cwd);
890 	if (!cwdw) {
891 		php_error_docref(NULL, E_WARNING, "CWD conversion failed");
892 		goto exit_fail;
893 	}
894 
895 	memset(&si, 0, sizeof(si));
896 	si.cb = sizeof(si);
897 	si.dwFlags = STARTF_USESTDHANDLES;
898 
899 	si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
900 	si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
901 	si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
902 
903 	/* redirect stdin/stdout/stderr if requested */
904 	for (i = 0; i < ndesc; i++) {
905 		switch(descriptors[i].index) {
906 			case 0:
907 				si.hStdInput = descriptors[i].childend;
908 				break;
909 			case 1:
910 				si.hStdOutput = descriptors[i].childend;
911 				break;
912 			case 2:
913 				si.hStdError = descriptors[i].childend;
914 				break;
915 		}
916 	}
917 
918 
919 	memset(&pi, 0, sizeof(pi));
920 
921 	if (suppress_errors) {
922 		old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX);
923 	}
924 
925 	dwCreateFlags = NORMAL_PRIORITY_CLASS;
926 	if(strcmp(sapi_module.name, "cli") != 0) {
927 		dwCreateFlags |= CREATE_NO_WINDOW;
928 	}
929 	if (create_process_group) {
930 		dwCreateFlags |= CREATE_NEW_PROCESS_GROUP;
931 	}
932 	if (create_new_console) {
933 		dwCreateFlags |= CREATE_NEW_CONSOLE;
934 	}
935 	envpw = php_win32_cp_env_any_to_w(env.envp);
936 	if (envpw) {
937 		dwCreateFlags |= CREATE_UNICODE_ENVIRONMENT;
938 	} else  {
939 		if (env.envp) {
940 			php_error_docref(NULL, E_WARNING, "ENV conversion failed");
941 			goto exit_fail;
942 		}
943 	}
944 
945 	cmdw = php_win32_cp_conv_any_to_w(command, strlen(command), &tmp_len);
946 	if (!cmdw) {
947 		php_error_docref(NULL, E_WARNING, "Command conversion failed");
948 		goto exit_fail;
949 	}
950 
951 	if (bypass_shell) {
952 		newprocok = CreateProcessW(NULL, cmdw, &security, &security, TRUE, dwCreateFlags, envpw, cwdw, &si, &pi);
953 	} else {
954 		int ret;
955 		size_t len;
956 		wchar_t *cmdw2;
957 
958 
959 		len = (sizeof(COMSPEC_NT) + sizeof(" /c ") + tmp_len + 1);
960 		cmdw2 = (wchar_t *)malloc(len * sizeof(wchar_t));
961 		if (!cmdw2) {
962 			php_error_docref(NULL, E_WARNING, "Command conversion failed");
963 			goto exit_fail;
964 		}
965 		ret = _snwprintf(cmdw2, len, L"%hs /c %s", COMSPEC_NT, cmdw);
966 
967 		if (-1 == ret) {
968 			free(cmdw2);
969 			php_error_docref(NULL, E_WARNING, "Command conversion failed");
970 			goto exit_fail;
971 		}
972 
973 		newprocok = CreateProcessW(NULL, cmdw2, &security, &security, TRUE, dwCreateFlags, envpw, cwdw, &si, &pi);
974 		free(cmdw2);
975 	}
976 
977 	free(cwdw);
978 	cwdw = NULL;
979 	free(cmdw);
980 	cmdw = NULL;
981 	free(envpw);
982 	envpw = NULL;
983 
984 	if (suppress_errors) {
985 		SetErrorMode(old_error_mode);
986 	}
987 
988 	if (FALSE == newprocok) {
989 		DWORD dw = GetLastError();
990 
991 		/* clean up all the descriptors */
992 		for (i = 0; i < ndesc; i++) {
993 			CloseHandle(descriptors[i].childend);
994 			if (descriptors[i].parentend) {
995 				CloseHandle(descriptors[i].parentend);
996 			}
997 		}
998 		php_error_docref(NULL, E_WARNING, "CreateProcess failed, error code - %u", dw);
999 		goto exit_fail;
1000 	}
1001 
1002 	childHandle = pi.hProcess;
1003 	child       = pi.dwProcessId;
1004 	CloseHandle(pi.hThread);
1005 #elif HAVE_FORK
1006 	/* the unix way */
1007 	child = fork();
1008 
1009 	if (child == 0) {
1010 		/* this is the child process */
1011 
1012 #if PHP_CAN_DO_PTS
1013 		if (dev_ptmx >= 0) {
1014 			int my_pid = getpid();
1015 
1016 #ifdef TIOCNOTTY
1017 			/* detach from original tty. Might only need this if isatty(0) is true */
1018 			ioctl(0,TIOCNOTTY,NULL);
1019 #else
1020 			setsid();
1021 #endif
1022 			/* become process group leader */
1023 			setpgid(my_pid, my_pid);
1024 			tcsetpgrp(0, my_pid);
1025 		}
1026 #endif
1027 
1028 #if PHP_CAN_DO_PTS
1029 		if (dev_ptmx >= 0) {
1030 			close(dev_ptmx);
1031 			close(slave_pty);
1032 		}
1033 #endif
1034 		/* close those descriptors that we just opened for the parent stuff,
1035 		 * dup new descriptors into required descriptors and close the original
1036 		 * cruft */
1037 		for (i = 0; i < ndesc; i++) {
1038 			switch (descriptors[i].mode & ~DESC_PARENT_MODE_WRITE) {
1039 				case DESC_PIPE:
1040 					close(descriptors[i].parentend);
1041 					break;
1042 			}
1043 			if (dup2(descriptors[i].childend, descriptors[i].index) < 0)
1044 				perror("dup2");
1045 			if (descriptors[i].childend != descriptors[i].index)
1046 				close(descriptors[i].childend);
1047 		}
1048 
1049 		if (cwd) {
1050 			php_ignore_value(chdir(cwd));
1051 		}
1052 
1053 		if (argv) {
1054 			/* execvpe() is non-portable, use environ instead. */
1055 			if (env.envarray) {
1056 				environ = env.envarray;
1057 			}
1058 			execvp(command, argv);
1059 		} else {
1060 			if (env.envarray) {
1061 				execle("/bin/sh", "sh", "-c", command, NULL, env.envarray);
1062 			} else {
1063 				execl("/bin/sh", "sh", "-c", command, NULL);
1064 			}
1065 		}
1066 		_exit(127);
1067 
1068 	} else if (child < 0) {
1069 		/* failed to fork() */
1070 
1071 		/* clean up all the descriptors */
1072 		for (i = 0; i < ndesc; i++) {
1073 			close(descriptors[i].childend);
1074 			if (descriptors[i].parentend)
1075 				close(descriptors[i].parentend);
1076 		}
1077 
1078 		php_error_docref(NULL, E_WARNING, "fork failed - %s", strerror(errno));
1079 
1080 		goto exit_fail;
1081 
1082 	}
1083 #else
1084 # error You lose (configure should not have let you get here)
1085 #endif
1086 	/* we forked/spawned and this is the parent */
1087 
1088 	pipes = zend_try_array_init(pipes);
1089 	if (!pipes) {
1090 		goto exit_fail;
1091 	}
1092 
1093 	proc = (struct php_process_handle*)pemalloc(sizeof(struct php_process_handle), is_persistent);
1094 	proc->is_persistent = is_persistent;
1095 	proc->command = command;
1096 	proc->pipes = pemalloc(sizeof(zend_resource *) * ndesc, is_persistent);
1097 	proc->npipes = ndesc;
1098 	proc->child = child;
1099 #ifdef PHP_WIN32
1100 	proc->childHandle = childHandle;
1101 #endif
1102 	proc->env = env;
1103 
1104 #if PHP_CAN_DO_PTS
1105 	if (dev_ptmx >= 0) {
1106 		close(dev_ptmx);
1107 		close(slave_pty);
1108 	}
1109 #endif
1110 
1111 	/* clean up all the child ends and then open streams on the parent
1112 	 * ends, where appropriate */
1113 	for (i = 0; i < ndesc; i++) {
1114 		char *mode_string=NULL;
1115 		php_stream *stream = NULL;
1116 
1117 		close_descriptor(descriptors[i].childend);
1118 
1119 		switch (descriptors[i].mode & ~DESC_PARENT_MODE_WRITE) {
1120 			case DESC_PIPE:
1121 				switch(descriptors[i].mode_flags) {
1122 #ifdef PHP_WIN32
1123 					case O_WRONLY|O_BINARY:
1124 						mode_string = "wb";
1125 						break;
1126 					case O_RDONLY|O_BINARY:
1127 						mode_string = "rb";
1128 						break;
1129 #endif
1130 					case O_WRONLY:
1131 						mode_string = "w";
1132 						break;
1133 					case O_RDONLY:
1134 						mode_string = "r";
1135 						break;
1136 					case O_RDWR:
1137 						mode_string = "r+";
1138 						break;
1139 				}
1140 #ifdef PHP_WIN32
1141 				stream = php_stream_fopen_from_fd(_open_osfhandle((zend_intptr_t)descriptors[i].parentend,
1142 							descriptors[i].mode_flags), mode_string, NULL);
1143 				php_stream_set_option(stream, PHP_STREAM_OPTION_PIPE_BLOCKING, blocking_pipes, NULL);
1144 #else
1145 				stream = php_stream_fopen_from_fd(descriptors[i].parentend, mode_string, NULL);
1146 # if defined(F_SETFD) && defined(FD_CLOEXEC)
1147 				/* mark the descriptor close-on-exec, so that it won't be inherited by potential other children */
1148 				fcntl(descriptors[i].parentend, F_SETFD, FD_CLOEXEC);
1149 # endif
1150 #endif
1151 				if (stream) {
1152 					zval retfp;
1153 
1154 					/* nasty hack; don't copy it */
1155 					stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
1156 
1157 					php_stream_to_zval(stream, &retfp);
1158 					add_index_zval(pipes, descriptors[i].index, &retfp);
1159 
1160 					proc->pipes[i] = Z_RES(retfp);
1161 					Z_ADDREF(retfp);
1162 				}
1163 				break;
1164 			default:
1165 				proc->pipes[i] = NULL;
1166 		}
1167 	}
1168 
1169 #ifndef PHP_WIN32
1170 	if (argv) {
1171 		char **arg = argv;
1172 		while (*arg != NULL) {
1173 			efree(*arg);
1174 			arg++;
1175 		}
1176 		efree(argv);
1177 	}
1178 #endif
1179 
1180 	efree(descriptors);
1181 	ZVAL_RES(return_value, zend_register_resource(proc, le_proc_open));
1182 	return;
1183 
1184 exit_fail:
1185 	if (descriptors) {
1186 		efree(descriptors);
1187 	}
1188 	_php_free_envp(env, is_persistent);
1189 	if (command) {
1190 		pefree(command, is_persistent);
1191 	}
1192 #ifdef PHP_WIN32
1193 	free(cwdw);
1194 	free(cmdw);
1195 	free(envpw);
1196 #else
1197 	if (argv) {
1198 		char **arg = argv;
1199 		while (*arg != NULL) {
1200 			efree(*arg);
1201 			arg++;
1202 		}
1203 		efree(argv);
1204 	}
1205 #endif
1206 #if PHP_CAN_DO_PTS
1207 	if (dev_ptmx >= 0) {
1208 		close(dev_ptmx);
1209 	}
1210 	if (slave_pty >= 0) {
1211 		close(slave_pty);
1212 	}
1213 #endif
1214 	RETURN_FALSE;
1215 
1216 }
1217 /* }}} */
1218 
1219 #endif /* PHP_CAN_SUPPORT_PROC_OPEN */
1220