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