xref: /PHP-5.3/ext/standard/proc_open.c (revision a2045ff3)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 5                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2013 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 /* $Id$ */
19 
20 #if 0 && (defined(__linux__) || defined(sun) || defined(__IRIX__))
21 # define _BSD_SOURCE 		/* linux wants this when XOPEN mode is on */
22 # define _BSD_COMPAT		/* irix: uint */
23 # define _XOPEN_SOURCE 500  /* turn on Unix98 */
24 # define __EXTENSIONS__	1	/* Solaris: uint */
25 #endif
26 
27 #include "php.h"
28 #include <stdio.h>
29 #include <ctype.h>
30 #include "php_string.h"
31 #include "safe_mode.h"
32 #include "ext/standard/head.h"
33 #include "ext/standard/basic_functions.h"
34 #include "ext/standard/file.h"
35 #include "exec.h"
36 #include "php_globals.h"
37 #include "SAPI.h"
38 
39 #ifdef NETWARE
40 #include <proc.h>
41 #include <library.h>
42 #endif
43 
44 #if HAVE_SYS_WAIT_H
45 #include <sys/wait.h>
46 #endif
47 #if HAVE_SIGNAL_H
48 #include <signal.h>
49 #endif
50 
51 #if HAVE_SYS_STAT_H
52 #include <sys/stat.h>
53 #endif
54 #if HAVE_FCNTL_H
55 #include <fcntl.h>
56 #endif
57 
58 /* This symbol is defined in ext/standard/config.m4.
59  * Essentially, it is set if you HAVE_FORK || PHP_WIN32
60  * Other platforms may modify that configure check and add suitable #ifdefs
61  * around the alternate code.
62  * */
63 #ifdef PHP_CAN_SUPPORT_PROC_OPEN
64 
65 #if 0 && HAVE_PTSNAME && HAVE_GRANTPT && HAVE_UNLOCKPT && HAVE_SYS_IOCTL_H && HAVE_TERMIOS_H
66 # include <sys/ioctl.h>
67 # include <termios.h>
68 # define PHP_CAN_DO_PTS	1
69 #endif
70 
71 #include "proc_open.h"
72 
73 static int le_proc_open;
74 
75 /* {{{ _php_array_to_envp */
_php_array_to_envp(zval * environment,int is_persistent TSRMLS_DC)76 static php_process_env_t _php_array_to_envp(zval *environment, int is_persistent TSRMLS_DC)
77 {
78 	zval **element;
79 	php_process_env_t env;
80 	char *string_key, *data;
81 #ifndef PHP_WIN32
82 	char **ep;
83 #endif
84 	char *p;
85 	uint string_length, cnt, l, sizeenv=0, el_len;
86 	ulong num_key;
87 	HashTable *target_hash;
88 	HashPosition pos;
89 
90 	memset(&env, 0, sizeof(env));
91 
92 	if (!environment) {
93 		return env;
94 	}
95 
96 	cnt = zend_hash_num_elements(Z_ARRVAL_P(environment));
97 
98 	if (cnt < 1) {
99 #ifndef PHP_WIN32
100 		env.envarray = (char **) pecalloc(1, sizeof(char *), is_persistent);
101 #endif
102 		env.envp = (char *) pecalloc(4, 1, is_persistent);
103 		return env;
104 	}
105 
106 	target_hash = HASH_OF(environment);
107 	if (!target_hash) {
108 		return env;
109 	}
110 
111 	/* first, we have to get the size of all the elements in the hash */
112 	for (zend_hash_internal_pointer_reset_ex(target_hash, &pos);
113 			zend_hash_get_current_data_ex(target_hash, (void **) &element, &pos) == SUCCESS;
114 			zend_hash_move_forward_ex(target_hash, &pos)) {
115 
116 		convert_to_string_ex(element);
117 		el_len = Z_STRLEN_PP(element);
118 		if (el_len == 0) {
119 			continue;
120 		}
121 
122 		sizeenv += el_len+1;
123 
124 		switch (zend_hash_get_current_key_ex(target_hash, &string_key, &string_length, &num_key, 0, &pos)) {
125 			case HASH_KEY_IS_STRING:
126 				if (string_length == 0) {
127 					continue;
128 				}
129 				sizeenv += string_length+1;
130 				break;
131 		}
132 	}
133 
134 #ifndef PHP_WIN32
135 	ep = env.envarray = (char **) pecalloc(cnt + 1, sizeof(char *), is_persistent);
136 #endif
137 	p = env.envp = (char *) pecalloc(sizeenv + 4, 1, is_persistent);
138 
139 	for (zend_hash_internal_pointer_reset_ex(target_hash, &pos);
140 			zend_hash_get_current_data_ex(target_hash, (void **) &element, &pos) == SUCCESS;
141 			zend_hash_move_forward_ex(target_hash, &pos)) {
142 
143 		convert_to_string_ex(element);
144 		el_len = Z_STRLEN_PP(element);
145 
146 		if (el_len == 0) {
147 			continue;
148 		}
149 
150 		data = Z_STRVAL_PP(element);
151 		switch (zend_hash_get_current_key_ex(target_hash, &string_key, &string_length, &num_key, 0, &pos)) {
152 			case HASH_KEY_IS_STRING:
153 				if (string_length == 0) {
154 					continue;
155 				}
156 				if (PG(safe_mode)) {
157 					/* Check the protected list */
158 					if (zend_hash_exists(&BG(sm_protected_env_vars), string_key, string_length - 1)) {
159 						php_error_docref(NULL TSRMLS_CC, E_WARNING, "Safe Mode warning: Cannot override protected environment variable '%s'", string_key);
160 						return env;
161 					}
162 					/* Check the allowed list */
163 					if (BG(sm_allowed_env_vars) && *BG(sm_allowed_env_vars)) {
164 						char *allowed_env_vars = estrdup(BG(sm_allowed_env_vars));
165 						char *strtok_buf = NULL;
166 						char *allowed_prefix = php_strtok_r(allowed_env_vars, ", ", &strtok_buf);
167 						zend_bool allowed = 0;
168 
169 						while (allowed_prefix) {
170 							if (!strncmp(allowed_prefix, string_key, strlen(allowed_prefix))) {
171 								allowed = 1;
172 								break;
173 							}
174 							allowed_prefix = php_strtok_r(NULL, ", ", &strtok_buf);
175 						}
176 						efree(allowed_env_vars);
177 						if (!allowed) {
178 							php_error_docref(NULL TSRMLS_CC, E_WARNING, "Safe Mode warning: Cannot set environment variable '%s' - it's not in the allowed list", string_key);
179 							return env;
180 						}
181 					}
182 				}
183 
184 				l = string_length + el_len + 1;
185 				memcpy(p, string_key, string_length);
186 				strncat(p, "=", 1);
187 				strncat(p, data, el_len);
188 
189 #ifndef PHP_WIN32
190 				*ep = p;
191 				++ep;
192 #endif
193 				p += l;
194 				break;
195 			case HASH_KEY_IS_LONG:
196 				memcpy(p,data,el_len);
197 #ifndef PHP_WIN32
198 				*ep = p;
199 				++ep;
200 #endif
201 				p += el_len + 1;
202 				break;
203 			case HASH_KEY_NON_EXISTANT:
204 				break;
205 		}
206 	}
207 
208 	assert((uint)(p - env.envp) <= sizeenv);
209 
210 	zend_hash_internal_pointer_reset_ex(target_hash, &pos);
211 
212 	return env;
213 }
214 /* }}} */
215 
216 /* {{{ _php_free_envp */
_php_free_envp(php_process_env_t env,int is_persistent)217 static void _php_free_envp(php_process_env_t env, int is_persistent)
218 {
219 #ifndef PHP_WIN32
220 	if (env.envarray) {
221 		pefree(env.envarray, is_persistent);
222 	}
223 #endif
224 	if (env.envp) {
225 		pefree(env.envp, is_persistent);
226 	}
227 }
228 /* }}} */
229 
230 /* {{{ proc_open_rsrc_dtor */
proc_open_rsrc_dtor(zend_rsrc_list_entry * rsrc TSRMLS_DC)231 static void proc_open_rsrc_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
232 {
233 	struct php_process_handle *proc = (struct php_process_handle*)rsrc->ptr;
234 	int i;
235 #ifdef PHP_WIN32
236 	DWORD wstatus;
237 #elif HAVE_SYS_WAIT_H
238 	int wstatus;
239 	pid_t wait_pid;
240 #endif
241 
242 	/* Close all handles to avoid a deadlock */
243 	for (i = 0; i < proc->npipes; i++) {
244 		if (proc->pipes[i] != 0) {
245 			zend_list_delete(proc->pipes[i]);
246 			proc->pipes[i] = 0;
247 		}
248 	}
249 
250 #ifdef PHP_WIN32
251 	WaitForSingleObject(proc->childHandle, INFINITE);
252 	GetExitCodeProcess(proc->childHandle, &wstatus);
253 	FG(pclose_ret) = wstatus;
254 	CloseHandle(proc->childHandle);
255 
256 #elif HAVE_SYS_WAIT_H
257 
258 	do {
259 		wait_pid = waitpid(proc->child, &wstatus, 0);
260 	} while (wait_pid == -1 && errno == EINTR);
261 
262 	if (wait_pid == -1) {
263 		FG(pclose_ret) = -1;
264 	} else {
265 		if (WIFEXITED(wstatus))
266 			wstatus = WEXITSTATUS(wstatus);
267 		FG(pclose_ret) = wstatus;
268 	}
269 
270 #else
271 	FG(pclose_ret) = -1;
272 #endif
273 	_php_free_envp(proc->env, proc->is_persistent);
274 	pefree(proc->command, proc->is_persistent);
275 	pefree(proc, proc->is_persistent);
276 }
277 /* }}} */
278 
279 /* {{{ php_make_safe_mode_command */
php_make_safe_mode_command(char * cmd,char ** safecmd,int is_persistent TSRMLS_DC)280 static int php_make_safe_mode_command(char *cmd, char **safecmd, int is_persistent TSRMLS_DC)
281 {
282 	int lcmd, larg0;
283 	char *space, *sep, *arg0;
284 
285 	if (!PG(safe_mode)) {
286 		*safecmd = pestrdup(cmd, is_persistent);
287 		return SUCCESS;
288 	}
289 
290 	lcmd = strlen(cmd);
291 
292 	arg0 = estrndup(cmd, lcmd);
293 
294 	space = memchr(arg0, ' ', lcmd);
295 	if (space) {
296 		*space = '\0';
297 		larg0 = space - arg0;
298 	} else {
299 		larg0 = lcmd;
300 	}
301 
302 	if (php_memnstr(arg0, "..", sizeof("..")-1, arg0 + larg0)) {
303 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "No '..' components allowed in path");
304 		efree(arg0);
305 		return FAILURE;
306 	}
307 
308 	sep = zend_memrchr(arg0, PHP_DIR_SEPARATOR, larg0);
309 
310 	spprintf(safecmd, 0, "%s%s%s%s", PG(safe_mode_exec_dir), (sep ? sep : "/"), (sep ? "" : arg0), (space ? cmd + larg0 : ""));
311 
312 	efree(arg0);
313 	arg0 = php_escape_shell_cmd(*safecmd);
314 	efree(*safecmd);
315 	if (is_persistent) {
316 		*safecmd = pestrdup(arg0, 1);
317 		efree(arg0);
318 	} else {
319 		*safecmd = arg0;
320 	}
321 
322 	return SUCCESS;
323 }
324 /* }}} */
325 
326 /* {{{ PHP_MINIT_FUNCTION(proc_open) */
PHP_MINIT_FUNCTION(proc_open)327 PHP_MINIT_FUNCTION(proc_open)
328 {
329 	le_proc_open = zend_register_list_destructors_ex(proc_open_rsrc_dtor, NULL, "process", module_number);
330 	return SUCCESS;
331 }
332 /* }}} */
333 
334 /* {{{ proto bool proc_terminate(resource process [, long signal])
335    kill a process opened by proc_open */
PHP_FUNCTION(proc_terminate)336 PHP_FUNCTION(proc_terminate)
337 {
338 	zval *zproc;
339 	struct php_process_handle *proc;
340 	long sig_no = SIGTERM;
341 
342 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r|l", &zproc, &sig_no) == FAILURE) {
343 		RETURN_FALSE;
344 	}
345 
346 	ZEND_FETCH_RESOURCE(proc, struct php_process_handle *, &zproc, -1, "process", le_proc_open);
347 
348 #ifdef PHP_WIN32
349 	if (TerminateProcess(proc->childHandle, 255)) {
350 		RETURN_TRUE;
351 	} else {
352 		RETURN_FALSE;
353 	}
354 #else
355 	if (kill(proc->child, sig_no) == 0) {
356 		RETURN_TRUE;
357 	} else {
358 		RETURN_FALSE;
359 	}
360 #endif
361 }
362 /* }}} */
363 
364 /* {{{ proto int proc_close(resource process)
365    close a process opened by proc_open */
PHP_FUNCTION(proc_close)366 PHP_FUNCTION(proc_close)
367 {
368 	zval *zproc;
369 	struct php_process_handle *proc;
370 
371 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zproc) == FAILURE) {
372 		RETURN_FALSE;
373 	}
374 
375 	ZEND_FETCH_RESOURCE(proc, struct php_process_handle *, &zproc, -1, "process", le_proc_open);
376 
377 	zend_list_delete(Z_LVAL_P(zproc));
378 	RETURN_LONG(FG(pclose_ret));
379 }
380 /* }}} */
381 
382 /* {{{ proto array proc_get_status(resource process)
383    get information about a process opened by proc_open */
PHP_FUNCTION(proc_get_status)384 PHP_FUNCTION(proc_get_status)
385 {
386 	zval *zproc;
387 	struct php_process_handle *proc;
388 #ifdef PHP_WIN32
389 	DWORD wstatus;
390 #elif HAVE_SYS_WAIT_H
391 	int wstatus;
392 	pid_t wait_pid;
393 #endif
394 	int running = 1, signaled = 0, stopped = 0;
395 	int exitcode = -1, termsig = 0, stopsig = 0;
396 
397 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zproc) == FAILURE) {
398 		RETURN_FALSE;
399 	}
400 
401 	ZEND_FETCH_RESOURCE(proc, struct php_process_handle *, &zproc, -1, "process", le_proc_open);
402 
403 	array_init(return_value);
404 
405 	add_assoc_string(return_value, "command", proc->command, 1);
406 	add_assoc_long(return_value, "pid", (long) proc->child);
407 
408 #ifdef PHP_WIN32
409 
410 	GetExitCodeProcess(proc->childHandle, &wstatus);
411 
412 	running = wstatus == STILL_ACTIVE;
413 	exitcode = running ? -1 : wstatus;
414 
415 #elif HAVE_SYS_WAIT_H
416 
417 	errno = 0;
418 	wait_pid = waitpid(proc->child, &wstatus, WNOHANG|WUNTRACED);
419 
420 	if (wait_pid == proc->child) {
421 		if (WIFEXITED(wstatus)) {
422 			running = 0;
423 			exitcode = WEXITSTATUS(wstatus);
424 		}
425 		if (WIFSIGNALED(wstatus)) {
426 			running = 0;
427 			signaled = 1;
428 #ifdef NETWARE
429 			termsig = WIFTERMSIG(wstatus);
430 #else
431 			termsig = WTERMSIG(wstatus);
432 #endif
433 		}
434 		if (WIFSTOPPED(wstatus)) {
435 			stopped = 1;
436 			stopsig = WSTOPSIG(wstatus);
437 		}
438 	} else if (wait_pid == -1) {
439 		running = 0;
440 	}
441 #endif
442 
443 	add_assoc_bool(return_value, "running", running);
444 	add_assoc_bool(return_value, "signaled", signaled);
445 	add_assoc_bool(return_value, "stopped", stopped);
446 	add_assoc_long(return_value, "exitcode", exitcode);
447 	add_assoc_long(return_value, "termsig", termsig);
448 	add_assoc_long(return_value, "stopsig", stopsig);
449 }
450 /* }}} */
451 
452 /* {{{ handy definitions for portability/readability */
453 #ifdef PHP_WIN32
454 # define pipe(pair)		(CreatePipe(&pair[0], &pair[1], &security, 0) ? 0 : -1)
455 
456 # define COMSPEC_NT	"cmd.exe"
457 
dup_handle(HANDLE src,BOOL inherit,BOOL closeorig)458 static inline HANDLE dup_handle(HANDLE src, BOOL inherit, BOOL closeorig)
459 {
460 	HANDLE copy, self = GetCurrentProcess();
461 
462 	if (!DuplicateHandle(self, src, self, &copy, 0, inherit, DUPLICATE_SAME_ACCESS |
463 				(closeorig ? DUPLICATE_CLOSE_SOURCE : 0)))
464 		return NULL;
465 	return copy;
466 }
467 
dup_fd_as_handle(int fd)468 static inline HANDLE dup_fd_as_handle(int fd)
469 {
470 	return dup_handle((HANDLE)_get_osfhandle(fd), TRUE, FALSE);
471 }
472 
473 # define close_descriptor(fd)	CloseHandle(fd)
474 #else
475 # define close_descriptor(fd)	close(fd)
476 #endif
477 
478 #define DESC_PIPE		1
479 #define DESC_FILE		2
480 #define DESC_PARENT_MODE_WRITE	8
481 
482 struct php_proc_open_descriptor_item {
483 	int index; 							/* desired fd number in child process */
484 	php_file_descriptor_t parentend, childend;	/* fds for pipes in parent/child */
485 	int mode;							/* mode for proc_open code */
486 	int mode_flags;						/* mode flags for opening fds */
487 };
488 /* }}} */
489 
490 /* {{{ proto resource proc_open(string command, array descriptorspec, array &pipes [, string cwd [, array env [, array other_options]]])
491    Run a process with more control over it's file descriptors */
PHP_FUNCTION(proc_open)492 PHP_FUNCTION(proc_open)
493 {
494 	char *command, *cwd=NULL;
495 	int command_len, cwd_len = 0;
496 	zval *descriptorspec;
497 	zval *pipes;
498 	zval *environment = NULL;
499 	zval *other_options = NULL;
500 	php_process_env_t env;
501 	int ndesc = 0;
502 	int i;
503 	zval **descitem = NULL;
504 	HashPosition pos;
505 	struct php_proc_open_descriptor_item descriptors[PHP_PROC_OPEN_MAX_DESCRIPTORS];
506 #ifdef PHP_WIN32
507 	PROCESS_INFORMATION pi;
508 	HANDLE childHandle;
509 	STARTUPINFO si;
510 	BOOL newprocok;
511 	SECURITY_ATTRIBUTES security;
512 	DWORD dwCreateFlags = 0;
513 	char *command_with_cmd;
514 	UINT old_error_mode;
515 #endif
516 #ifdef NETWARE
517 	char** child_argv = NULL;
518 	char* command_dup = NULL;
519 	char* orig_cwd = NULL;
520 	int command_num_args = 0;
521 	wiring_t channel;
522 #endif
523 	php_process_id_t child;
524 	struct php_process_handle *proc;
525 	int is_persistent = 0; /* TODO: ensure that persistent procs will work */
526 #ifdef PHP_WIN32
527 	int suppress_errors = 0;
528 	int bypass_shell = 0;
529 #endif
530 #if PHP_CAN_DO_PTS
531 	php_file_descriptor_t dev_ptmx = -1;	/* master */
532 	php_file_descriptor_t slave_pty = -1;
533 #endif
534 
535 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "saz|s!a!a!", &command,
536 				&command_len, &descriptorspec, &pipes, &cwd, &cwd_len, &environment,
537 				&other_options) == FAILURE) {
538 		RETURN_FALSE;
539 	}
540 
541 	if (FAILURE == php_make_safe_mode_command(command, &command, is_persistent TSRMLS_CC)) {
542 		RETURN_FALSE;
543 	}
544 
545 #ifdef PHP_WIN32
546 	if (other_options) {
547 		zval **item;
548 		if (SUCCESS == zend_hash_find(Z_ARRVAL_P(other_options), "suppress_errors", sizeof("suppress_errors"), (void**)&item)) {
549 			if ((Z_TYPE_PP(item) == IS_BOOL || Z_TYPE_PP(item) == IS_LONG) &&
550 			    Z_LVAL_PP(item)) {
551 				suppress_errors = 1;
552 			}
553 		}
554 		if (SUCCESS == zend_hash_find(Z_ARRVAL_P(other_options), "bypass_shell", sizeof("bypass_shell"), (void**)&item)) {
555 			if ((Z_TYPE_PP(item) == IS_BOOL || Z_TYPE_PP(item) == IS_LONG) &&
556 			    Z_LVAL_PP(item)) {
557 				bypass_shell = 1;
558 			}
559 		}
560 	}
561 #endif
562 
563 	command_len = strlen(command);
564 
565 	if (environment) {
566 		env = _php_array_to_envp(environment, is_persistent TSRMLS_CC);
567 	} else {
568 		memset(&env, 0, sizeof(env));
569 	}
570 
571 	memset(descriptors, 0, sizeof(descriptors));
572 
573 #ifdef PHP_WIN32
574 	/* we use this to allow the child to inherit handles */
575 	memset(&security, 0, sizeof(security));
576 	security.nLength = sizeof(security);
577 	security.bInheritHandle = TRUE;
578 	security.lpSecurityDescriptor = NULL;
579 #endif
580 
581 	/* walk the descriptor spec and set up files/pipes */
582 	zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(descriptorspec), &pos);
583 	while (zend_hash_get_current_data_ex(Z_ARRVAL_P(descriptorspec), (void **)&descitem, &pos) == SUCCESS) {
584 		char *str_index;
585 		ulong nindex;
586 		zval **ztype;
587 
588 		str_index = NULL;
589 		zend_hash_get_current_key_ex(Z_ARRVAL_P(descriptorspec), &str_index, NULL, &nindex, 0, &pos);
590 
591 		if (str_index) {
592 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "descriptor spec must be an integer indexed array");
593 			goto exit_fail;
594 		}
595 
596 		descriptors[ndesc].index = nindex;
597 
598 		if (Z_TYPE_PP(descitem) == IS_RESOURCE) {
599 			/* should be a stream - try and dup the descriptor */
600 			php_stream *stream;
601 			int fd;
602 
603 			php_stream_from_zval(stream, descitem);
604 
605 			if (FAILURE == php_stream_cast(stream, PHP_STREAM_AS_FD, (void **)&fd, REPORT_ERRORS)) {
606 				goto exit_fail;
607 			}
608 
609 #ifdef PHP_WIN32
610 			descriptors[ndesc].childend = dup_fd_as_handle(fd);
611 			if (descriptors[ndesc].childend == NULL) {
612 				php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to dup File-Handle for descriptor %d", nindex);
613 				goto exit_fail;
614 			}
615 #else
616 			descriptors[ndesc].childend = dup(fd);
617 			if (descriptors[ndesc].childend < 0) {
618 				php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to dup File-Handle for descriptor %ld - %s", nindex, strerror(errno));
619 				goto exit_fail;
620 			}
621 #endif
622 			descriptors[ndesc].mode = DESC_FILE;
623 
624 		} else if (Z_TYPE_PP(descitem) != IS_ARRAY) {
625 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Descriptor item must be either an array or a File-Handle");
626 			goto exit_fail;
627 		} else {
628 
629 			if (zend_hash_index_find(Z_ARRVAL_PP(descitem), 0, (void **)&ztype) == SUCCESS) {
630 				convert_to_string_ex(ztype);
631 			} else {
632 				php_error_docref(NULL TSRMLS_CC, E_WARNING, "Missing handle qualifier in array");
633 				goto exit_fail;
634 			}
635 
636 			if (strcmp(Z_STRVAL_PP(ztype), "pipe") == 0) {
637 				php_file_descriptor_t newpipe[2];
638 				zval **zmode;
639 
640 				if (zend_hash_index_find(Z_ARRVAL_PP(descitem), 1, (void **)&zmode) == SUCCESS) {
641 					convert_to_string_ex(zmode);
642 				} else {
643 					php_error_docref(NULL TSRMLS_CC, E_WARNING, "Missing mode parameter for 'pipe'");
644 					goto exit_fail;
645 				}
646 
647 				descriptors[ndesc].mode = DESC_PIPE;
648 
649 				if (0 != pipe(newpipe)) {
650 					php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to create pipe %s", strerror(errno));
651 					goto exit_fail;
652 				}
653 
654 				if (strncmp(Z_STRVAL_PP(zmode), "w", 1) != 0) {
655 					descriptors[ndesc].parentend = newpipe[1];
656 					descriptors[ndesc].childend = newpipe[0];
657 					descriptors[ndesc].mode |= DESC_PARENT_MODE_WRITE;
658 				} else {
659 					descriptors[ndesc].parentend = newpipe[0];
660 					descriptors[ndesc].childend = newpipe[1];
661 				}
662 #ifdef PHP_WIN32
663 				/* don't let the child inherit the parent side of the pipe */
664 				descriptors[ndesc].parentend = dup_handle(descriptors[ndesc].parentend, FALSE, TRUE);
665 #endif
666 				descriptors[ndesc].mode_flags = descriptors[ndesc].mode & DESC_PARENT_MODE_WRITE ? O_WRONLY : O_RDONLY;
667 #ifdef PHP_WIN32
668 				if (Z_STRLEN_PP(zmode) >= 2 && Z_STRVAL_PP(zmode)[1] == 'b')
669 					descriptors[ndesc].mode_flags |= O_BINARY;
670 #endif
671 
672 			} else if (strcmp(Z_STRVAL_PP(ztype), "file") == 0) {
673 				zval **zfile, **zmode;
674 				int fd;
675 				php_stream *stream;
676 
677 				descriptors[ndesc].mode = DESC_FILE;
678 
679 				if (zend_hash_index_find(Z_ARRVAL_PP(descitem), 1, (void **)&zfile) == SUCCESS) {
680 					convert_to_string_ex(zfile);
681 				} else {
682 					php_error_docref(NULL TSRMLS_CC, E_WARNING, "Missing file name parameter for 'file'");
683 					goto exit_fail;
684 				}
685 
686 				if (zend_hash_index_find(Z_ARRVAL_PP(descitem), 2, (void **)&zmode) == SUCCESS) {
687 					convert_to_string_ex(zmode);
688 				} else {
689 					php_error_docref(NULL TSRMLS_CC, E_WARNING, "Missing mode parameter for 'file'");
690 					goto exit_fail;
691 				}
692 
693 				/* try a wrapper */
694 				stream = php_stream_open_wrapper(Z_STRVAL_PP(zfile), Z_STRVAL_PP(zmode),
695 						ENFORCE_SAFE_MODE|REPORT_ERRORS|STREAM_WILL_CAST, NULL);
696 
697 				/* force into an fd */
698 				if (stream == NULL || FAILURE == php_stream_cast(stream,
699 							PHP_STREAM_CAST_RELEASE|PHP_STREAM_AS_FD,
700 							(void **)&fd, REPORT_ERRORS)) {
701 					goto exit_fail;
702 				}
703 
704 #ifdef PHP_WIN32
705 				descriptors[ndesc].childend = dup_fd_as_handle(fd);
706 				_close(fd);
707 
708 				/* simulate the append mode by fseeking to the end of the file
709 				this introduces a potential race-condition, but it is the best we can do, though */
710 				if (strchr(Z_STRVAL_PP(zmode), 'a')) {
711 					SetFilePointer(descriptors[ndesc].childend, 0, NULL, FILE_END);
712 				}
713 #else
714 				descriptors[ndesc].childend = fd;
715 #endif
716 			} else if (strcmp(Z_STRVAL_PP(ztype), "pty") == 0) {
717 #if PHP_CAN_DO_PTS
718 				if (dev_ptmx == -1) {
719 					/* open things up */
720 					dev_ptmx = open("/dev/ptmx", O_RDWR);
721 					if (dev_ptmx == -1) {
722 						php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to open /dev/ptmx, errno %d", errno);
723 						goto exit_fail;
724 					}
725 					grantpt(dev_ptmx);
726 					unlockpt(dev_ptmx);
727 					slave_pty = open(ptsname(dev_ptmx), O_RDWR);
728 
729 					if (slave_pty == -1) {
730 						php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to open slave pty, errno %d", errno);
731 						goto exit_fail;
732 					}
733 				}
734 				descriptors[ndesc].mode = DESC_PIPE;
735 				descriptors[ndesc].childend = dup(slave_pty);
736 				descriptors[ndesc].parentend = dup(dev_ptmx);
737 				descriptors[ndesc].mode_flags = O_RDWR;
738 #else
739 				php_error_docref(NULL TSRMLS_CC, E_WARNING, "pty pseudo terminal not supported on this system");
740 				goto exit_fail;
741 #endif
742 			} else {
743 				php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s is not a valid descriptor spec/mode", Z_STRVAL_PP(ztype));
744 				goto exit_fail;
745 			}
746 		}
747 
748 		zend_hash_move_forward_ex(Z_ARRVAL_P(descriptorspec), &pos);
749 		if (++ndesc == PHP_PROC_OPEN_MAX_DESCRIPTORS)
750 			break;
751 	}
752 
753 #ifdef PHP_WIN32
754 	if (cwd == NULL) {
755 		char cur_cwd[MAXPATHLEN];
756 		char *getcwd_result;
757 		getcwd_result = VCWD_GETCWD(cur_cwd, MAXPATHLEN);
758 		if (!getcwd_result) {
759 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot get current directory");
760 			goto exit_fail;
761 		}
762 	}
763 
764 	memset(&si, 0, sizeof(si));
765 	si.cb = sizeof(si);
766 	si.dwFlags = STARTF_USESTDHANDLES;
767 
768 	si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
769 	si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
770 	si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
771 
772 	/* redirect stdin/stdout/stderr if requested */
773 	for (i = 0; i < ndesc; i++) {
774 		switch(descriptors[i].index) {
775 			case 0:
776 				si.hStdInput = descriptors[i].childend;
777 				break;
778 			case 1:
779 				si.hStdOutput = descriptors[i].childend;
780 				break;
781 			case 2:
782 				si.hStdError = descriptors[i].childend;
783 				break;
784 		}
785 	}
786 
787 
788 	memset(&pi, 0, sizeof(pi));
789 
790 	if (suppress_errors) {
791 		old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX);
792 	}
793 
794 	dwCreateFlags = NORMAL_PRIORITY_CLASS;
795 	if(strcmp(sapi_module.name, "cli") != 0) {
796 		dwCreateFlags |= CREATE_NO_WINDOW;
797 	}
798 
799 	if (bypass_shell) {
800 		newprocok = CreateProcess(NULL, command, &security, &security, TRUE, dwCreateFlags, env.envp, cwd, &si, &pi);
801 	} else {
802 		spprintf(&command_with_cmd, 0, "%s /c %s", COMSPEC_NT, command);
803 
804 		newprocok = CreateProcess(NULL, command_with_cmd, &security, &security, TRUE, dwCreateFlags, env.envp, cwd, &si, &pi);
805 
806 		efree(command_with_cmd);
807 	}
808 
809 	if (suppress_errors) {
810 		SetErrorMode(old_error_mode);
811 	}
812 
813 	if (FALSE == newprocok) {
814 		DWORD dw = GetLastError();
815 
816 		/* clean up all the descriptors */
817 		for (i = 0; i < ndesc; i++) {
818 			CloseHandle(descriptors[i].childend);
819 			if (descriptors[i].parentend) {
820 				CloseHandle(descriptors[i].parentend);
821 			}
822 		}
823 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "CreateProcess failed, error code - %u", dw);
824 		goto exit_fail;
825 	}
826 
827 	childHandle = pi.hProcess;
828 	child       = pi.dwProcessId;
829 	CloseHandle(pi.hThread);
830 
831 #elif defined(NETWARE)
832 	if (cwd) {
833 		orig_cwd = getcwd(NULL, PATH_MAX);
834 		chdir2(cwd);
835 	}
836 	channel.infd = descriptors[0].childend;
837 	channel.outfd = descriptors[1].childend;
838 	channel.errfd = -1;
839 	/* Duplicate the command as processing downwards will modify it*/
840 	command_dup = strdup(command);
841 	if (!command_dup) {
842 		goto exit_fail;
843 	}
844 	/* get a number of args */
845 	construct_argc_argv(command_dup, NULL, &command_num_args, NULL);
846 	child_argv = (char**) malloc((command_num_args + 1) * sizeof(char*));
847 	if(!child_argv) {
848 		free(command_dup);
849 		if (cwd && orig_cwd) {
850 			chdir2(orig_cwd);
851 			free(orig_cwd);
852 		}
853 	}
854 	/* fill the child arg vector */
855 	construct_argc_argv(command_dup, NULL, &command_num_args, child_argv);
856 	child_argv[command_num_args] = NULL;
857 	child = procve(child_argv[0], PROC_DETACHED|PROC_INHERIT_CWD, NULL, &channel, NULL, NULL, 0, NULL, (const char**)child_argv);
858 	free(child_argv);
859 	free(command_dup);
860 	if (cwd && orig_cwd) {
861 		chdir2(orig_cwd);
862 		free(orig_cwd);
863 	}
864 	if (child < 0) {
865 		/* failed to fork() */
866 		/* clean up all the descriptors */
867 		for (i = 0; i < ndesc; i++) {
868 			close(descriptors[i].childend);
869 			if (descriptors[i].parentend)
870 				close(descriptors[i].parentend);
871 		}
872 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "procve failed - %s", strerror(errno));
873 		goto exit_fail;
874 	}
875 #elif HAVE_FORK
876 	/* the unix way */
877 	child = fork();
878 
879 	if (child == 0) {
880 		/* this is the child process */
881 
882 #if PHP_CAN_DO_PTS
883 		if (dev_ptmx >= 0) {
884 			int my_pid = getpid();
885 
886 #ifdef TIOCNOTTY
887 			/* detach from original tty. Might only need this if isatty(0) is true */
888 			ioctl(0,TIOCNOTTY,NULL);
889 #else
890 			setsid();
891 #endif
892 			/* become process group leader */
893 			setpgid(my_pid, my_pid);
894 			tcsetpgrp(0, my_pid);
895 		}
896 #endif
897 
898 		/* close those descriptors that we just opened for the parent stuff,
899 		 * dup new descriptors into required descriptors and close the original
900 		 * cruft */
901 		for (i = 0; i < ndesc; i++) {
902 			switch (descriptors[i].mode & ~DESC_PARENT_MODE_WRITE) {
903 				case DESC_PIPE:
904 					close(descriptors[i].parentend);
905 					break;
906 			}
907 			if (dup2(descriptors[i].childend, descriptors[i].index) < 0)
908 				perror("dup2");
909 			if (descriptors[i].childend != descriptors[i].index)
910 				close(descriptors[i].childend);
911 		}
912 
913 #if PHP_CAN_DO_PTS
914 		if (dev_ptmx >= 0) {
915 			close(dev_ptmx);
916 			close(slave_pty);
917 		}
918 #endif
919 
920 		if (cwd) {
921 			chdir(cwd);
922 		}
923 
924 		if (env.envarray) {
925 			execle("/bin/sh", "sh", "-c", command, NULL, env.envarray);
926 		} else {
927 			execl("/bin/sh", "sh", "-c", command, NULL);
928 		}
929 		_exit(127);
930 
931 	} else if (child < 0) {
932 		/* failed to fork() */
933 
934 		/* clean up all the descriptors */
935 		for (i = 0; i < ndesc; i++) {
936 			close(descriptors[i].childend);
937 			if (descriptors[i].parentend)
938 				close(descriptors[i].parentend);
939 		}
940 
941 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "fork failed - %s", strerror(errno));
942 
943 		goto exit_fail;
944 
945 	}
946 #else
947 # error You lose (configure should not have let you get here)
948 #endif
949 	/* we forked/spawned and this is the parent */
950 
951 	proc = (struct php_process_handle*)pemalloc(sizeof(struct php_process_handle), is_persistent);
952 	proc->is_persistent = is_persistent;
953 	proc->command = command;
954 	proc->npipes = ndesc;
955 	proc->child = child;
956 #ifdef PHP_WIN32
957 	proc->childHandle = childHandle;
958 #endif
959 	proc->env = env;
960 
961 	if (pipes != NULL) {
962 		zval_dtor(pipes);
963 	}
964 	array_init(pipes);
965 
966 #if PHP_CAN_DO_PTS
967 	if (dev_ptmx >= 0) {
968 		close(dev_ptmx);
969 		close(slave_pty);
970 	}
971 #endif
972 
973 	/* clean up all the child ends and then open streams on the parent
974 	 * ends, where appropriate */
975 	for (i = 0; i < ndesc; i++) {
976 		char *mode_string=NULL;
977 		php_stream *stream = NULL;
978 
979 		close_descriptor(descriptors[i].childend);
980 
981 		switch (descriptors[i].mode & ~DESC_PARENT_MODE_WRITE) {
982 			case DESC_PIPE:
983 				switch(descriptors[i].mode_flags) {
984 #ifdef PHP_WIN32
985 					case O_WRONLY|O_BINARY:
986 						mode_string = "wb";
987 						break;
988 					case O_RDONLY|O_BINARY:
989 						mode_string = "rb";
990 						break;
991 #endif
992 					case O_WRONLY:
993 						mode_string = "w";
994 						break;
995 					case O_RDONLY:
996 						mode_string = "r";
997 						break;
998 					case O_RDWR:
999 						mode_string = "r+";
1000 						break;
1001 				}
1002 #ifdef PHP_WIN32
1003 				stream = php_stream_fopen_from_fd(_open_osfhandle((zend_intptr_t)descriptors[i].parentend,
1004 							descriptors[i].mode_flags), mode_string, NULL);
1005 #else
1006 				stream = php_stream_fopen_from_fd(descriptors[i].parentend, mode_string, NULL);
1007 # if defined(F_SETFD) && defined(FD_CLOEXEC)
1008 				/* mark the descriptor close-on-exec, so that it won't be inherited by potential other children */
1009 				fcntl(descriptors[i].parentend, F_SETFD, FD_CLOEXEC);
1010 # endif
1011 #endif
1012 				if (stream) {
1013 					zval *retfp;
1014 
1015 					/* nasty hack; don't copy it */
1016 					stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
1017 
1018 					MAKE_STD_ZVAL(retfp);
1019 					php_stream_to_zval(stream, retfp);
1020 					add_index_zval(pipes, descriptors[i].index, retfp);
1021 
1022 					proc->pipes[i] = Z_LVAL_P(retfp);
1023 				}
1024 				break;
1025 			default:
1026 				proc->pipes[i] = 0;
1027 		}
1028 	}
1029 
1030 	ZEND_REGISTER_RESOURCE(return_value, proc, le_proc_open);
1031 	return;
1032 
1033 exit_fail:
1034 	_php_free_envp(env, is_persistent);
1035 	pefree(command, is_persistent);
1036 #if PHP_CAN_DO_PTS
1037 	if (dev_ptmx >= 0) {
1038 		close(dev_ptmx);
1039 	}
1040 	if (slave_pty >= 0) {
1041 		close(slave_pty);
1042 	}
1043 #endif
1044 	RETURN_FALSE;
1045 
1046 }
1047 /* }}} */
1048 
1049 #endif /* PHP_CAN_SUPPORT_PROC_OPEN */
1050 
1051 /*
1052  * Local variables:
1053  * tab-width: 4
1054  * c-basic-offset: 4
1055  * End:
1056  * vim600: sw=4 ts=4 fdm=marker
1057  * vim<600: sw=4 ts=4
1058  */
1059