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, ©, 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