php-internal-docs 8.4.8
Unofficial docs for php/php-src
Loading...
Searching...
No Matches
proc_open.c
Go to the documentation of this file.
1/*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | https://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Author: Wez Furlong <wez@thebrainroom.com> |
14 +----------------------------------------------------------------------+
15 */
16
17#include "php.h"
18#include <ctype.h>
19#include <signal.h>
21#include "ext/standard/file.h"
22#include "exec.h"
23#include "SAPI.h"
24#include "main/php_network.h"
25#include "zend_smart_str.h"
26#ifdef PHP_WIN32
27# include "win32/sockets.h"
28#endif
29
30#ifdef HAVE_SYS_WAIT_H
31#include <sys/wait.h>
32#endif
33
34#ifdef HAVE_FCNTL_H
35#include <fcntl.h>
36#endif
37
38#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP
39/* Only defined on glibc >= 2.29, FreeBSD CURRENT, musl >= 1.1.24,
40 * MacOS Catalina or later..
41 * It should be posible to modify this so it is also
42 * used in older systems when $cwd == NULL but care must be taken
43 * as at least glibc < 2.24 has a legacy implementation known
44 * to be really buggy.
45 */
46#include <spawn.h>
47#define USE_POSIX_SPAWN
48#endif
49
50/* This symbol is defined in ext/standard/config.m4.
51 * Essentially, it is set if you HAVE_FORK || PHP_WIN32
52 * Other platforms may modify that configure check and add suitable #ifdefs
53 * around the alternate code. */
54#ifdef PHP_CAN_SUPPORT_PROC_OPEN
55
56#ifdef HAVE_OPENPTY
57# ifdef HAVE_PTY_H
58# include <pty.h>
59# elif defined(__FreeBSD__)
60/* FreeBSD defines `openpty` in <libutil.h> */
61# include <libutil.h>
62# elif defined(__NetBSD__) || defined(__DragonFly__)
63/* On recent NetBSD/DragonFlyBSD releases the emalloc, estrdup ... calls had been introduced in libutil */
64# if defined(__NetBSD__)
65# include <sys/termios.h>
66# else
67# include <termios.h>
68# endif
69extern int openpty(int *, int *, char *, struct termios *, struct winsize *);
70# elif defined(__sun)
71# include <termios.h>
72# else
73/* Mac OS X (and some BSDs) define `openpty` in <util.h> */
74# include <util.h>
75# endif
76#elif defined(__sun)
77# include <fcntl.h>
78# include <stropts.h>
79# include <termios.h>
80# define HAVE_OPENPTY 1
81
82/* Solaris before version 11.4 and Illumos do not have any openpty implementation */
83int openpty(int *master, int *slave, char *name, struct termios *termp, struct winsize *winp)
84{
85 int fd, sd;
86 const char *slaveid;
87
88 assert(master);
89 assert(slave);
90
91 sd = *master = *slave = -1;
92 fd = open("/dev/ptmx", O_NOCTTY|O_RDWR);
93 if (fd == -1) {
94 return -1;
95 }
96 /* Checking if we can have to the pseudo terminal */
97 if (grantpt(fd) != 0 || unlockpt(fd) != 0) {
98 goto fail;
99 }
100 slaveid = ptsname(fd);
101 if (!slaveid) {
102 goto fail;
103 }
104
105 /* Getting the slave path and pushing pseudo terminal */
106 sd = open(slaveid, O_NOCTTY|O_RDONLY);
107 if (sd == -1 || ioctl(sd, I_PUSH, "ptem") == -1) {
108 goto fail;
109 }
110 if (termp) {
111 if (tcgetattr(sd, termp) < 0) {
112 goto fail;
113 }
114 }
115 if (winp) {
116 if (ioctl(sd, TIOCSWINSZ, winp) == -1) {
117 goto fail;
118 }
119 }
120
121 *slave = sd;
122 *master = fd;
123 return 0;
124fail:
125 if (sd != -1) {
126 close(sd);
127 }
128 if (fd != -1) {
129 close(fd);
130 }
131 return -1;
132}
133#endif
134
135#include "proc_open.h"
136
137static int le_proc_open; /* Resource number for `proc` resources */
138
139/* {{{ _php_array_to_envp
140 * Process the `environment` argument to `proc_open`
141 * Convert into data structures which can be passed to underlying OS APIs like `exec` on POSIX or
142 * `CreateProcessW` on Win32 */
143static php_process_env _php_array_to_envp(zval *environment)
144{
145 zval *element;
146 php_process_env env;
147 zend_string *key, *str;
148#ifndef PHP_WIN32
149 char **ep;
150#endif
151 char *p;
152 size_t sizeenv = 0;
153 HashTable *env_hash; /* temporary PHP array used as helper */
154
155 memset(&env, 0, sizeof(env));
156
157 if (!environment) {
158 return env;
159 }
160
161 uint32_t cnt = zend_hash_num_elements(Z_ARRVAL_P(environment));
162
163 if (cnt < 1) {
164#ifndef PHP_WIN32
165 env.envarray = (char **) ecalloc(1, sizeof(char *));
166#endif
167 env.envp = (char *) ecalloc(4, 1);
168 return env;
169 }
170
171 ALLOC_HASHTABLE(env_hash);
172 zend_hash_init(env_hash, cnt, NULL, NULL, 0);
173
174 /* first, we have to get the size of all the elements in the hash */
175 ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(environment), key, element) {
176 str = zval_get_string(element);
177
178 if (ZSTR_LEN(str) == 0) {
180 continue;
181 }
182
183 sizeenv += ZSTR_LEN(str) + 1;
184
185 if (key && ZSTR_LEN(key)) {
186 sizeenv += ZSTR_LEN(key) + 1;
187 zend_hash_add_ptr(env_hash, key, str);
188 } else {
189 zend_hash_next_index_insert_ptr(env_hash, str);
190 }
192
193#ifndef PHP_WIN32
194 ep = env.envarray = (char **) ecalloc(cnt + 1, sizeof(char *));
195#endif
196 p = env.envp = (char *) ecalloc(sizeenv + 4, 1);
197
198 ZEND_HASH_FOREACH_STR_KEY_PTR(env_hash, key, str) {
199#ifndef PHP_WIN32
200 *ep = p;
201 ++ep;
202#endif
203
204 if (key) {
205 p = zend_mempcpy(p, ZSTR_VAL(key), ZSTR_LEN(key));
206 *p++ = '=';
207 }
208
209 p = zend_mempcpy(p, ZSTR_VAL(str), ZSTR_LEN(str));
210 *p++ = '\0';
213
214 assert((uint32_t)(p - env.envp) <= sizeenv);
215
216 zend_hash_destroy(env_hash);
217 FREE_HASHTABLE(env_hash);
218
219 return env;
220}
221/* }}} */
222
223/* {{{ _php_free_envp
224 * Free the structures allocated by `_php_array_to_envp` */
225static void _php_free_envp(php_process_env env)
226{
227#ifndef PHP_WIN32
228 if (env.envarray) {
229 efree(env.envarray);
230 }
231#endif
232 if (env.envp) {
233 efree(env.envp);
234 }
235}
236/* }}} */
237
238#ifdef HAVE_SYS_WAIT_H
239static pid_t waitpid_cached(php_process_handle *proc, int *wait_status, int options)
240{
241 if (proc->has_cached_exit_wait_status) {
242 *wait_status = proc->cached_exit_wait_status_value;
243 return proc->child;
244 }
245
246 pid_t wait_pid = waitpid(proc->child, wait_status, options);
247
248 /* The "exit" status is the final status of the process.
249 * If we were to cache the status unconditionally,
250 * we would return stale statuses in the future after the process continues. */
251 if (wait_pid > 0 && WIFEXITED(*wait_status)) {
252 proc->has_cached_exit_wait_status = true;
253 proc->cached_exit_wait_status_value = *wait_status;
254 }
255
256 return wait_pid;
257}
258#endif
259
260/* {{{ proc_open_rsrc_dtor
261 * Free `proc` resource, either because all references to it were dropped or because `pclose` or
262 * `proc_close` were called */
263static void proc_open_rsrc_dtor(zend_resource *rsrc)
264{
266#ifdef PHP_WIN32
267 DWORD wstatus;
268#elif HAVE_SYS_WAIT_H
269 int wstatus;
270 int waitpid_options = 0;
271 pid_t wait_pid;
272#endif
273
274 /* Close all handles to avoid a deadlock */
275 for (int i = 0; i < proc->npipes; i++) {
276 if (proc->pipes[i] != NULL) {
277 GC_DELREF(proc->pipes[i]);
278 zend_list_close(proc->pipes[i]);
279 proc->pipes[i] = NULL;
280 }
281 }
282
283 /* `pclose_wait` tells us: Are we freeing this resource because `pclose` or `proc_close` were
284 * called? If so, we need to wait until the child process exits, because its exit code is
285 * needed as the return value of those functions.
286 * But if we're freeing the resource because of GC, don't wait. */
287#ifdef PHP_WIN32
288 if (FG(pclose_wait)) {
289 WaitForSingleObject(proc->childHandle, INFINITE);
290 }
291 GetExitCodeProcess(proc->childHandle, &wstatus);
292 if (wstatus == STILL_ACTIVE) {
293 FG(pclose_ret) = -1;
294 } else {
295 FG(pclose_ret) = wstatus;
296 }
297 CloseHandle(proc->childHandle);
298
299#elif HAVE_SYS_WAIT_H
300 if (!FG(pclose_wait)) {
301 waitpid_options = WNOHANG;
302 }
303 do {
304 wait_pid = waitpid_cached(proc, &wstatus, waitpid_options);
305 } while (wait_pid == -1 && errno == EINTR);
306
307 if (wait_pid <= 0) {
308 FG(pclose_ret) = -1;
309 } else {
310 if (WIFEXITED(wstatus)) {
311 wstatus = WEXITSTATUS(wstatus);
312 }
313 FG(pclose_ret) = wstatus;
314 }
315
316#else
317 FG(pclose_ret) = -1;
318#endif
319
320 _php_free_envp(proc->env);
321 efree(proc->pipes);
322 zend_string_release_ex(proc->command, false);
323 efree(proc);
324}
325/* }}} */
326
327/* {{{ PHP_MINIT_FUNCTION(proc_open) */
329{
330 le_proc_open = zend_register_list_destructors_ex(proc_open_rsrc_dtor, NULL, "process",
331 module_number);
332 return SUCCESS;
333}
334/* }}} */
335
336/* {{{ Kill a process opened by `proc_open` */
338{
339 zval *zproc;
340 php_process_handle *proc;
341 zend_long sig_no = SIGTERM;
342
344 Z_PARAM_RESOURCE(zproc)
346 Z_PARAM_LONG(sig_no)
348
349 proc = (php_process_handle*)zend_fetch_resource(Z_RES_P(zproc), "process", le_proc_open);
350 if (proc == NULL) {
352 }
353
354#ifdef PHP_WIN32
355 RETURN_BOOL(TerminateProcess(proc->childHandle, 255));
356#else
357 RETURN_BOOL(kill(proc->child, sig_no) == 0);
358#endif
359}
360/* }}} */
361
362/* {{{ Close a process opened by `proc_open` */
364{
365 zval *zproc;
366 php_process_handle *proc;
367
369 Z_PARAM_RESOURCE(zproc)
371
372 proc = (php_process_handle*)zend_fetch_resource(Z_RES_P(zproc), "process", le_proc_open);
373 if (proc == NULL) {
375 }
376
377 FG(pclose_wait) = 1; /* See comment in `proc_open_rsrc_dtor` */
378 zend_list_close(Z_RES_P(zproc));
379 FG(pclose_wait) = 0;
380 RETURN_LONG(FG(pclose_ret));
381}
382/* }}} */
383
384/* {{{ Get information about a process opened by `proc_open` */
386{
387 zval *zproc;
388 php_process_handle *proc;
389#ifdef PHP_WIN32
390 DWORD wstatus;
391#elif HAVE_SYS_WAIT_H
392 int wstatus;
393 pid_t wait_pid;
394#endif
395 bool running = 1, signaled = 0, stopped = 0;
396 int exitcode = -1, termsig = 0, stopsig = 0;
397
399 Z_PARAM_RESOURCE(zproc)
401
402 proc = (php_process_handle*)zend_fetch_resource(Z_RES_P(zproc), "process", le_proc_open);
403 if (proc == NULL) {
405 }
406
408 add_assoc_str(return_value, "command", zend_string_copy(proc->command));
409 add_assoc_long(return_value, "pid", (zend_long)proc->child);
410
411#ifdef PHP_WIN32
412 GetExitCodeProcess(proc->childHandle, &wstatus);
413 running = wstatus == STILL_ACTIVE;
414 exitcode = running ? -1 : wstatus;
415
416 /* The status is always available on Windows and will always read the same,
417 * even if the child has already exited. This is because the result stays available
418 * until the child handle is closed. Hence no caching is used on Windows. */
419 add_assoc_bool(return_value, "cached", false);
420#elif HAVE_SYS_WAIT_H
421 wait_pid = waitpid_cached(proc, &wstatus, WNOHANG|WUNTRACED);
422
423 if (wait_pid == proc->child) {
424 if (WIFEXITED(wstatus)) {
425 running = 0;
426 exitcode = WEXITSTATUS(wstatus);
427 }
428 if (WIFSIGNALED(wstatus)) {
429 running = 0;
430 signaled = 1;
431 termsig = WTERMSIG(wstatus);
432 }
433 if (WIFSTOPPED(wstatus)) {
434 stopped = 1;
435 stopsig = WSTOPSIG(wstatus);
436 }
437 } else if (wait_pid == -1) {
438 /* The only error which could occur here is ECHILD, which means that the PID we were
439 * looking for either does not exist or is not a child of this process */
440 running = 0;
441 }
442
443 add_assoc_bool(return_value, "cached", proc->has_cached_exit_wait_status);
444#endif
445
446 add_assoc_bool(return_value, "running", running);
447 add_assoc_bool(return_value, "signaled", signaled);
448 add_assoc_bool(return_value, "stopped", stopped);
449 add_assoc_long(return_value, "exitcode", exitcode);
450 add_assoc_long(return_value, "termsig", termsig);
451 add_assoc_long(return_value, "stopsig", stopsig);
452}
453/* }}} */
454
455#ifdef PHP_WIN32
456
457/* We use this to allow child processes to inherit handles
458 * One static instance can be shared and used for all calls to `proc_open`, since the values are
459 * never changed */
460SECURITY_ATTRIBUTES php_proc_open_security = {
461 .nLength = sizeof(SECURITY_ATTRIBUTES),
462 .lpSecurityDescriptor = NULL,
463 .bInheritHandle = TRUE
464};
465
466# define pipe(pair) (CreatePipe(&pair[0], &pair[1], &php_proc_open_security, 0) ? 0 : -1)
467
468# define COMSPEC_NT "cmd.exe"
469
470static inline HANDLE dup_handle(HANDLE src, BOOL inherit, BOOL closeorig)
471{
472 HANDLE copy, self = GetCurrentProcess();
473
474 if (!DuplicateHandle(self, src, self, &copy, 0, inherit, DUPLICATE_SAME_ACCESS |
475 (closeorig ? DUPLICATE_CLOSE_SOURCE : 0)))
476 return NULL;
477 return copy;
478}
479
480static inline HANDLE dup_fd_as_handle(int fd)
481{
482 return dup_handle((HANDLE)_get_osfhandle(fd), TRUE, FALSE);
483}
484
485# define close_descriptor(fd) CloseHandle(fd)
486#else /* !PHP_WIN32 */
487# define close_descriptor(fd) close(fd)
488#endif
489
490/* Determines the type of a descriptor item. */
491typedef enum _descriptor_type {
492 DESCRIPTOR_TYPE_STD,
493 DESCRIPTOR_TYPE_PIPE,
494 DESCRIPTOR_TYPE_SOCKET
495} descriptor_type;
496
497/* One instance of this struct is created for each item in `$descriptorspec` argument to `proc_open`
498 * They are used within `proc_open` and freed before it returns */
499typedef struct _descriptorspec_item {
500 int index; /* desired FD # in child process */
501 descriptor_type type;
502 php_file_descriptor_t childend; /* FD # opened for use in child
503 * (will be copied to `index` in child) */
504 php_file_descriptor_t parentend; /* FD # opened for use in parent
505 * (for pipes only; will be 0 otherwise) */
506 int mode_flags; /* mode for opening FDs: r/o, r/w, binary (on Win32), etc */
507} descriptorspec_item;
508
509static zend_string *get_valid_arg_string(zval *zv, int elem_num) {
510 zend_string *str = zval_get_string(zv);
511 if (!str) {
512 return NULL;
513 }
514
515 if (elem_num == 1 && ZSTR_LEN(str) == 0) {
516 zend_value_error("First element must contain a non-empty program name");
517 zend_string_release(str);
518 return NULL;
519 }
520
521 if (strlen(ZSTR_VAL(str)) != ZSTR_LEN(str)) {
522 zend_value_error("Command array element %d contains a null byte", elem_num);
523 zend_string_release(str);
524 return NULL;
525 }
526
527 return str;
528}
529
530#ifdef PHP_WIN32
531static void append_backslashes(smart_str *str, size_t num_bs)
532{
533 for (size_t i = 0; i < num_bs; i++) {
534 smart_str_appendc(str, '\\');
535 }
536}
537
538const char *special_chars = "()!^\"<>&|%";
539
540static bool is_special_character_present(const zend_string *arg)
541{
542 for (size_t i = 0; i < ZSTR_LEN(arg); ++i) {
543 if (strchr(special_chars, ZSTR_VAL(arg)[i]) != NULL) {
544 return true;
545 }
546 }
547 return false;
548}
549
550/* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments and
551 * https://learn.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way */
552static void append_win_escaped_arg(smart_str *str, zend_string *arg, bool is_cmd_argument)
553{
554 size_t num_bs = 0;
555 bool has_special_character = false;
556
557 if (is_cmd_argument) {
558 has_special_character = is_special_character_present(arg);
559 if (has_special_character) {
560 /* Escape double quote with ^ if executed by cmd.exe. */
561 smart_str_appendc(str, '^');
562 }
563 }
564 smart_str_appendc(str, '"');
565 for (size_t i = 0; i < ZSTR_LEN(arg); ++i) {
566 char c = ZSTR_VAL(arg)[i];
567 if (c == '\\') {
568 num_bs++;
569 continue;
570 }
571
572 if (c == '"') {
573 /* Backslashes before " need to be doubled. */
574 num_bs = num_bs * 2 + 1;
575 }
576 append_backslashes(str, num_bs);
577 if (has_special_character && strchr(special_chars, c) != NULL) {
578 /* Escape special chars with ^ if executed by cmd.exe. */
579 smart_str_appendc(str, '^');
580 }
581 smart_str_appendc(str, c);
582 num_bs = 0;
583 }
584 append_backslashes(str, num_bs * 2);
585 if (has_special_character) {
586 /* Escape double quote with ^ if executed by cmd.exe. */
587 smart_str_appendc(str, '^');
588 }
589 smart_str_appendc(str, '"');
590}
591
592static bool is_executed_by_cmd(const char *prog_name, size_t prog_name_length)
593{
594 size_t out_len;
595 WCHAR long_name[MAX_PATH];
596 WCHAR full_name[MAX_PATH];
597 LPWSTR file_part = NULL;
598
599 wchar_t *prog_name_wide = php_win32_cp_conv_any_to_w(prog_name, prog_name_length, &out_len);
600
601 if (GetLongPathNameW(prog_name_wide, long_name, MAX_PATH) == 0) {
602 /* This can fail for example with ERROR_FILE_NOT_FOUND (short path resolution only works for existing files)
603 * in which case we'll pass the path verbatim to the FullPath transformation. */
604 lstrcpynW(long_name, prog_name_wide, MAX_PATH);
605 }
606
607 free(prog_name_wide);
608 prog_name_wide = NULL;
609
610 if (GetFullPathNameW(long_name, MAX_PATH, full_name, &file_part) == 0 || file_part == NULL) {
611 return false;
612 }
613
614 bool uses_cmd = false;
615 if (_wcsicmp(file_part, L"cmd.exe") == 0 || _wcsicmp(file_part, L"cmd") == 0) {
616 uses_cmd = true;
617 } else {
618 const WCHAR *extension_dot = wcsrchr(file_part, L'.');
619 if (extension_dot && (_wcsicmp(extension_dot, L".bat") == 0 || _wcsicmp(extension_dot, L".cmd") == 0)) {
620 uses_cmd = true;
621 }
622 }
623
624 return uses_cmd;
625}
626
627static zend_string *create_win_command_from_args(HashTable *args)
628{
629 smart_str str = {0};
630 zval *arg_zv;
631 bool is_prog_name = true;
632 bool is_cmd_execution = false;
633 int elem_num = 0;
634
635 ZEND_HASH_FOREACH_VAL(args, arg_zv) {
636 zend_string *arg_str = get_valid_arg_string(arg_zv, ++elem_num);
637 if (!arg_str) {
638 smart_str_free(&str);
639 return NULL;
640 }
641
642 if (is_prog_name) {
643 is_cmd_execution = is_executed_by_cmd(ZSTR_VAL(arg_str), ZSTR_LEN(arg_str));
644 } else {
645 smart_str_appendc(&str, ' ');
646 }
647
648 append_win_escaped_arg(&str, arg_str, !is_prog_name && is_cmd_execution);
649
650 is_prog_name = 0;
651 zend_string_release(arg_str);
653 smart_str_0(&str);
654 return str.s;
655}
656
657/* Get a boolean option from the `other_options` array which can be passed to `proc_open`.
658 * (Currently, all options apply on Windows only.) */
659static bool get_option(zval *other_options, char *opt_name, size_t opt_name_len)
660{
661 HashTable *opt_ary = Z_ARRVAL_P(other_options);
662 zval *item = zend_hash_str_find_deref(opt_ary, opt_name, opt_name_len);
663 return item != NULL &&
664 (Z_TYPE_P(item) == IS_TRUE ||
665 ((Z_TYPE_P(item) == IS_LONG) && Z_LVAL_P(item)));
666}
667
668/* Initialize STARTUPINFOW struct, used on Windows when spawning a process.
669 * Ref: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfow */
670static void init_startup_info(STARTUPINFOW *si, descriptorspec_item *descriptors, int ndesc)
671{
672 memset(si, 0, sizeof(STARTUPINFOW));
673 si->cb = sizeof(STARTUPINFOW);
674 si->dwFlags = STARTF_USESTDHANDLES;
675
676 si->hStdInput = GetStdHandle(STD_INPUT_HANDLE);
677 si->hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
678 si->hStdError = GetStdHandle(STD_ERROR_HANDLE);
679
680 /* redirect stdin/stdout/stderr if requested */
681 for (int i = 0; i < ndesc; i++) {
682 switch (descriptors[i].index) {
683 case 0:
684 si->hStdInput = descriptors[i].childend;
685 break;
686 case 1:
687 si->hStdOutput = descriptors[i].childend;
688 break;
689 case 2:
690 si->hStdError = descriptors[i].childend;
691 break;
692 }
693 }
694}
695
696static void init_process_info(PROCESS_INFORMATION *pi)
697{
698 memset(&pi, 0, sizeof(pi));
699}
700
701/* on success, returns length of *comspec, which then needs to be efree'd by caller */
702static size_t find_comspec_nt(wchar_t **comspec)
703{
704 zend_string *path = NULL;
705 wchar_t *pathw = NULL;
706 wchar_t *bufp = NULL;
707 DWORD buflen = MAX_PATH, len = 0;
708
709 path = php_getenv("PATH", 4);
710 if (path == NULL) {
711 goto out;
712 }
713 pathw = php_win32_cp_any_to_w(ZSTR_VAL(path));
714 if (pathw == NULL) {
715 goto out;
716 }
717 bufp = emalloc(buflen * sizeof(wchar_t));
718 do {
719 /* the first call to SearchPathW() fails if the buffer is too small,
720 * what is unlikely but possible; to avoid an explicit second call to
721 * SeachPathW() and the error handling, we're looping */
722 len = SearchPathW(pathw, L"cmd.exe", NULL, buflen, bufp, NULL);
723 if (len == 0) {
724 goto out;
725 }
726 if (len < buflen) {
727 break;
728 }
729 buflen = len;
730 bufp = erealloc(bufp, buflen * sizeof(wchar_t));
731 } while (1);
732 *comspec = bufp;
733
734out:
735 if (path != NULL) {
736 zend_string_release(path);
737 }
738 if (pathw != NULL) {
739 free(pathw);
740 }
741 if (bufp != NULL && bufp != *comspec) {
742 efree(bufp);
743 }
744 return len;
745}
746
747static zend_result convert_command_to_use_shell(wchar_t **cmdw, size_t cmdw_len)
748{
749 wchar_t *comspec;
750 size_t len = find_comspec_nt(&comspec);
751 if (len == 0) {
752 php_error_docref(NULL, E_WARNING, "Command conversion failed");
753 return FAILURE;
754 }
755 len += sizeof(" /s /c ") + cmdw_len + 3;
756 wchar_t *cmdw_shell = (wchar_t *)malloc(len * sizeof(wchar_t));
757
758 if (cmdw_shell == NULL) {
759 efree(comspec);
760 php_error_docref(NULL, E_WARNING, "Command conversion failed");
761 return FAILURE;
762 }
763
764 if (_snwprintf(cmdw_shell, len, L"%s /s /c \"%s\"", comspec, *cmdw) == -1) {
765 efree(comspec);
766 free(cmdw_shell);
767 php_error_docref(NULL, E_WARNING, "Command conversion failed");
768 return FAILURE;
769 }
770
771 efree(comspec);
772 free(*cmdw);
773 *cmdw = cmdw_shell;
774
775 return SUCCESS;
776}
777#endif
778
779/* Convert command parameter array passed as first argument to `proc_open` into command string */
780static zend_string* get_command_from_array(HashTable *array, char ***argv, int num_elems)
781{
782 zval *arg_zv;
783 zend_string *command = NULL;
784 int i = 0;
785
786 *argv = safe_emalloc(sizeof(char *), num_elems + 1, 0);
787
788 ZEND_HASH_FOREACH_VAL(array, arg_zv) {
789 zend_string *arg_str = get_valid_arg_string(arg_zv, i + 1);
790 if (!arg_str) {
791 /* Terminate with NULL so exit_fail code knows how many entries to free */
792 (*argv)[i] = NULL;
793 if (command != NULL) {
794 zend_string_release_ex(command, false);
795 }
796 return NULL;
797 }
798
799 if (i == 0) {
800 command = zend_string_copy(arg_str);
801 }
802
803 (*argv)[i++] = estrdup(ZSTR_VAL(arg_str));
804 zend_string_release(arg_str);
806
807 (*argv)[i] = NULL;
808 return command;
809}
810
811static descriptorspec_item* alloc_descriptor_array(HashTable *descriptorspec)
812{
813 uint32_t ndescriptors = zend_hash_num_elements(descriptorspec);
814 return ecalloc(ndescriptors, sizeof(descriptorspec_item));
815}
816
817static zend_string* get_string_parameter(zval *array, int index, char *param_name)
818{
819 zval *array_item;
820 if ((array_item = zend_hash_index_find(Z_ARRVAL_P(array), index)) == NULL) {
821 zend_value_error("Missing %s", param_name);
822 return NULL;
823 }
824 return zval_try_get_string(array_item);
825}
826
827static zend_result set_proc_descriptor_to_blackhole(descriptorspec_item *desc)
828{
829#ifdef PHP_WIN32
830 desc->childend = CreateFileA("nul", GENERIC_READ | GENERIC_WRITE,
831 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
832 if (desc->childend == NULL) {
833 php_error_docref(NULL, E_WARNING, "Failed to open nul");
834 return FAILURE;
835 }
836#else
837 desc->childend = open("/dev/null", O_RDWR);
838 if (desc->childend < 0) {
839 php_error_docref(NULL, E_WARNING, "Failed to open /dev/null: %s", strerror(errno));
840 return FAILURE;
841 }
842#endif
843 return SUCCESS;
844}
845
846static zend_result set_proc_descriptor_to_pty(descriptorspec_item *desc, int *master_fd, int *slave_fd)
847{
848#ifdef HAVE_OPENPTY
849 /* All FDs set to PTY in the child process will go to the slave end of the same PTY.
850 * Likewise, all the corresponding entries in `$pipes` in the parent will all go to the master
851 * end of the same PTY.
852 * If this is the first descriptorspec set to 'pty', find an available PTY and get master and
853 * slave FDs. */
854 if (*master_fd == -1) {
855 if (openpty(master_fd, slave_fd, NULL, NULL, NULL)) {
856 php_error_docref(NULL, E_WARNING, "Could not open PTY (pseudoterminal): %s", strerror(errno));
857 return FAILURE;
858 }
859 }
860
861 desc->type = DESCRIPTOR_TYPE_PIPE;
862 desc->childend = dup(*slave_fd);
863 desc->parentend = dup(*master_fd);
864 desc->mode_flags = O_RDWR;
865 return SUCCESS;
866#else
867 php_error_docref(NULL, E_WARNING, "PTY (pseudoterminal) not supported on this system");
868 return FAILURE;
869#endif
870}
871
872/* Mark the descriptor close-on-exec, so it won't be inherited by children */
873static php_file_descriptor_t make_descriptor_cloexec(php_file_descriptor_t fd)
874{
875#ifdef PHP_WIN32
876 return dup_handle(fd, FALSE, TRUE);
877#else
878#if defined(F_SETFD) && defined(FD_CLOEXEC)
879 fcntl(fd, F_SETFD, FD_CLOEXEC);
880#endif
881 return fd;
882#endif
883}
884
885static zend_result set_proc_descriptor_to_pipe(descriptorspec_item *desc, zend_string *zmode)
886{
887 php_file_descriptor_t newpipe[2];
888
889 if (pipe(newpipe)) {
890 php_error_docref(NULL, E_WARNING, "Unable to create pipe %s", strerror(errno));
891 return FAILURE;
892 }
893
894 desc->type = DESCRIPTOR_TYPE_PIPE;
895
896 if (!zend_string_starts_with_literal(zmode, "w")) {
897 desc->parentend = newpipe[1];
898 desc->childend = newpipe[0];
899 desc->mode_flags = O_WRONLY;
900 } else {
901 desc->parentend = newpipe[0];
902 desc->childend = newpipe[1];
903 desc->mode_flags = O_RDONLY;
904 }
905
906 desc->parentend = make_descriptor_cloexec(desc->parentend);
907
908#ifdef PHP_WIN32
909 if (ZSTR_LEN(zmode) >= 2 && ZSTR_VAL(zmode)[1] == 'b')
910 desc->mode_flags |= O_BINARY;
911#endif
912
913 return SUCCESS;
914}
915
916#ifdef PHP_WIN32
917#define create_socketpair(socks) socketpair_win32(AF_INET, SOCK_STREAM, 0, (socks), 0)
918#else
919#define create_socketpair(socks) socketpair(AF_UNIX, SOCK_STREAM, 0, (socks))
920#endif
921
922static zend_result set_proc_descriptor_to_socket(descriptorspec_item *desc)
923{
924 php_socket_t sock[2];
925
926 if (create_socketpair(sock)) {
928 php_error_docref(NULL, E_WARNING, "Unable to create socket pair: %s", ZSTR_VAL(err));
929 zend_string_release(err);
930 return FAILURE;
931 }
932
933 desc->type = DESCRIPTOR_TYPE_SOCKET;
934 desc->parentend = make_descriptor_cloexec((php_file_descriptor_t) sock[0]);
935
936 /* Pass sock[1] to child because it will never use overlapped IO on Windows. */
937 desc->childend = (php_file_descriptor_t) sock[1];
938
939 return SUCCESS;
940}
941
942static zend_result set_proc_descriptor_to_file(descriptorspec_item *desc, zend_string *file_path,
943 zend_string *file_mode)
944{
946
947 /* try a wrapper */
948 php_stream *stream = php_stream_open_wrapper(ZSTR_VAL(file_path), ZSTR_VAL(file_mode),
950 if (stream == NULL) {
951 return FAILURE;
952 }
953
954 /* force into an fd */
957 return FAILURE;
958 }
959
960#ifdef PHP_WIN32
961 desc->childend = dup_fd_as_handle((int)fd);
962 _close((int)fd);
963
964 /* Simulate the append mode by fseeking to the end of the file
965 * This introduces a potential race condition, but it is the best we can do */
966 if (strchr(ZSTR_VAL(file_mode), 'a')) {
967 SetFilePointer(desc->childend, 0, NULL, FILE_END);
968 }
969#else
970 desc->childend = fd;
971#endif
972 return SUCCESS;
973}
974
975static zend_result dup_proc_descriptor(php_file_descriptor_t from, php_file_descriptor_t *to,
976 zend_ulong nindex)
977{
978#ifdef PHP_WIN32
979 *to = dup_handle(from, TRUE, FALSE);
980 if (*to == NULL) {
981 php_error_docref(NULL, E_WARNING, "Failed to dup() for descriptor " ZEND_LONG_FMT, nindex);
982 return FAILURE;
983 }
984#else
985 *to = dup(from);
986 if (*to < 0) {
987 php_error_docref(NULL, E_WARNING, "Failed to dup() for descriptor " ZEND_LONG_FMT ": %s",
988 nindex, strerror(errno));
989 return FAILURE;
990 }
991#endif
992 return SUCCESS;
993}
994
995static zend_result redirect_proc_descriptor(descriptorspec_item *desc, int target,
996 descriptorspec_item *descriptors, int ndesc, int nindex)
997{
999
1000 for (int i = 0; i < ndesc; i++) {
1001 if (descriptors[i].index == target) {
1002 redirect_to = descriptors[i].childend;
1003 break;
1004 }
1005 }
1006
1007 if (redirect_to == PHP_INVALID_FD) { /* Didn't find the index we wanted */
1008 if (target < 0 || target > 2) {
1009 php_error_docref(NULL, E_WARNING, "Redirection target %d not found", target);
1010 return FAILURE;
1011 }
1012
1013 /* Support referring to a stdin/stdout/stderr pipe adopted from the parent,
1014 * which happens whenever an explicit override is not provided. */
1015#ifndef PHP_WIN32
1016 redirect_to = target;
1017#else
1018 switch (target) {
1019 case 0: redirect_to = GetStdHandle(STD_INPUT_HANDLE); break;
1020 case 1: redirect_to = GetStdHandle(STD_OUTPUT_HANDLE); break;
1021 case 2: redirect_to = GetStdHandle(STD_ERROR_HANDLE); break;
1023 }
1024#endif
1025 }
1026
1027 return dup_proc_descriptor(redirect_to, &desc->childend, nindex);
1028}
1029
1030/* Process one item from `$descriptorspec` argument to `proc_open` */
1031static zend_result set_proc_descriptor_from_array(zval *descitem, descriptorspec_item *descriptors,
1032 int ndesc, int nindex, int *pty_master_fd, int *pty_slave_fd) {
1033 zend_string *ztype = get_string_parameter(descitem, 0, "handle qualifier");
1034 if (!ztype) {
1035 return FAILURE;
1036 }
1037
1038 zend_string *zmode = NULL, *zfile = NULL;
1040
1041 if (zend_string_equals_literal(ztype, "pipe")) {
1042 /* Set descriptor to pipe */
1043 zmode = get_string_parameter(descitem, 1, "mode parameter for 'pipe'");
1044 if (zmode == NULL) {
1045 goto finish;
1046 }
1047 retval = set_proc_descriptor_to_pipe(&descriptors[ndesc], zmode);
1048 } else if (zend_string_equals_literal(ztype, "socket")) {
1049 /* Set descriptor to socketpair */
1050 retval = set_proc_descriptor_to_socket(&descriptors[ndesc]);
1051 } else if (zend_string_equals(ztype, ZSTR_KNOWN(ZEND_STR_FILE))) {
1052 /* Set descriptor to file */
1053 if ((zfile = get_string_parameter(descitem, 1, "file name parameter for 'file'")) == NULL) {
1054 goto finish;
1055 }
1056 if ((zmode = get_string_parameter(descitem, 2, "mode parameter for 'file'")) == NULL) {
1057 goto finish;
1058 }
1059 retval = set_proc_descriptor_to_file(&descriptors[ndesc], zfile, zmode);
1060 } else if (zend_string_equals_literal(ztype, "redirect")) {
1061 /* Redirect descriptor to whatever another descriptor is set to */
1062 zval *ztarget = zend_hash_index_find_deref(Z_ARRVAL_P(descitem), 1);
1063 if (!ztarget) {
1064 zend_value_error("Missing redirection target");
1065 goto finish;
1066 }
1067 if (Z_TYPE_P(ztarget) != IS_LONG) {
1068 zend_value_error("Redirection target must be of type int, %s given", zend_zval_value_name(ztarget));
1069 goto finish;
1070 }
1071
1072 retval = redirect_proc_descriptor(
1073 &descriptors[ndesc], (int)Z_LVAL_P(ztarget), descriptors, ndesc, nindex);
1074 } else if (zend_string_equals(ztype, ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE))) {
1075 /* Set descriptor to blackhole (discard all data written) */
1076 retval = set_proc_descriptor_to_blackhole(&descriptors[ndesc]);
1077 } else if (zend_string_equals_literal(ztype, "pty")) {
1078 /* Set descriptor to slave end of PTY */
1079 retval = set_proc_descriptor_to_pty(&descriptors[ndesc], pty_master_fd, pty_slave_fd);
1080 } else {
1081 php_error_docref(NULL, E_WARNING, "%s is not a valid descriptor spec/mode", ZSTR_VAL(ztype));
1082 goto finish;
1083 }
1084
1085finish:
1086 if (zmode) zend_string_release(zmode);
1087 if (zfile) zend_string_release(zfile);
1088 zend_string_release(ztype);
1089 return retval;
1090}
1091
1092static zend_result set_proc_descriptor_from_resource(zval *resource, descriptorspec_item *desc, int nindex)
1093{
1094 /* Should be a stream - try and dup the descriptor */
1095 php_stream *stream = (php_stream*)zend_fetch_resource(Z_RES_P(resource), "stream",
1097 if (stream == NULL) {
1098 return FAILURE;
1099 }
1100
1103 if (status == FAILURE) {
1104 return FAILURE;
1105 }
1106
1107#ifdef PHP_WIN32
1108 php_file_descriptor_t fd_t = (php_file_descriptor_t)_get_osfhandle(fd);
1109#else
1111#endif
1112 return dup_proc_descriptor(fd_t, &desc->childend, nindex);
1113}
1114
1115#ifndef PHP_WIN32
1116#if defined(USE_POSIX_SPAWN)
1117static zend_result close_parentends_of_pipes(posix_spawn_file_actions_t * actions, descriptorspec_item *descriptors, int ndesc)
1118{
1119 int r;
1120 for (int i = 0; i < ndesc; i++) {
1121 if (descriptors[i].type != DESCRIPTOR_TYPE_STD) {
1122 r = posix_spawn_file_actions_addclose(actions, descriptors[i].parentend);
1123 if (r != 0) {
1124 php_error_docref(NULL, E_WARNING, "Cannot close file descriptor %d: %s", descriptors[i].parentend, strerror(r));
1125 return FAILURE;
1126 }
1127 }
1128 if (descriptors[i].childend != descriptors[i].index) {
1129 r = posix_spawn_file_actions_adddup2(actions, descriptors[i].childend, descriptors[i].index);
1130 if (r != 0) {
1131 php_error_docref(NULL, E_WARNING, "Unable to copy file descriptor %d (for pipe) into "
1132 "file descriptor %d: %s", descriptors[i].childend, descriptors[i].index, strerror(r));
1133 return FAILURE;
1134 }
1135 r = posix_spawn_file_actions_addclose(actions, descriptors[i].childend);
1136 if (r != 0) {
1137 php_error_docref(NULL, E_WARNING, "Cannot close file descriptor %d: %s", descriptors[i].childend, strerror(r));
1138 return FAILURE;
1139 }
1140 }
1141 }
1142
1143 return SUCCESS;
1144}
1145#else
1146static zend_result close_parentends_of_pipes(descriptorspec_item *descriptors, int ndesc)
1147{
1148 /* We are running in child process
1149 * Close the 'parent end' of pipes which were opened before forking/spawning
1150 * Also, dup() the child end of all pipes as necessary so they will use the FD
1151 * number which the user requested */
1152 for (int i = 0; i < ndesc; i++) {
1153 if (descriptors[i].type != DESCRIPTOR_TYPE_STD) {
1154 close(descriptors[i].parentend);
1155 }
1156 if (descriptors[i].childend != descriptors[i].index) {
1157 if (dup2(descriptors[i].childend, descriptors[i].index) < 0) {
1158 php_error_docref(NULL, E_WARNING, "Unable to copy file descriptor %d (for pipe) into " \
1159 "file descriptor %d: %s", descriptors[i].childend, descriptors[i].index, strerror(errno));
1160 return FAILURE;
1161 }
1162 close(descriptors[i].childend);
1163 }
1164 }
1165
1166 return SUCCESS;
1167}
1168#endif
1169#endif
1170
1171static void close_all_descriptors(descriptorspec_item *descriptors, int ndesc)
1172{
1173 for (int i = 0; i < ndesc; i++) {
1174 close_descriptor(descriptors[i].childend);
1175 if (descriptors[i].parentend)
1176 close_descriptor(descriptors[i].parentend);
1177 }
1178}
1179
1180static void efree_argv(char **argv)
1181{
1182 if (argv) {
1183 char **arg = argv;
1184 while (*arg != NULL) {
1185 efree(*arg);
1186 arg++;
1187 }
1188 efree(argv);
1189 }
1190}
1191
1192/* {{{ Execute a command, with specified files used for input/output */
1194{
1195 zend_string *command_str;
1196 HashTable *command_ht;
1197 HashTable *descriptorspec; /* Mandatory argument */
1198 zval *pipes; /* Mandatory argument */
1199 char *cwd = NULL; /* Optional argument */
1200 size_t cwd_len = 0; /* Optional argument */
1201 zval *environment = NULL, *other_options = NULL; /* Optional arguments */
1202
1203 php_process_env env;
1204 int ndesc = 0;
1205 int i;
1206 zval *descitem = NULL;
1207 zend_string *str_index;
1208 zend_ulong nindex;
1209 descriptorspec_item *descriptors = NULL;
1210#ifdef PHP_WIN32
1211 PROCESS_INFORMATION pi;
1212 HANDLE childHandle;
1213 STARTUPINFOW si;
1214 BOOL newprocok;
1215 DWORD dwCreateFlags = 0;
1216 UINT old_error_mode;
1217 char cur_cwd[MAXPATHLEN];
1218 wchar_t *cmdw = NULL, *cwdw = NULL, *envpw = NULL;
1219 size_t cmdw_len;
1220 bool suppress_errors = 0;
1221 bool bypass_shell = 0;
1222 bool blocking_pipes = 0;
1223 bool create_process_group = 0;
1224 bool create_new_console = 0;
1225#else
1226 char **argv = NULL;
1227#endif
1228 int pty_master_fd = -1, pty_slave_fd = -1;
1229 php_process_id_t child;
1230 php_process_handle *proc;
1231
1233 Z_PARAM_ARRAY_HT_OR_STR(command_ht, command_str)
1234 Z_PARAM_ARRAY_HT(descriptorspec)
1235 Z_PARAM_ZVAL(pipes)
1238 Z_PARAM_ARRAY_OR_NULL(environment)
1239 Z_PARAM_ARRAY_OR_NULL(other_options)
1241
1242 memset(&env, 0, sizeof(env));
1243
1244 if (command_ht) {
1245 uint32_t num_elems = zend_hash_num_elements(command_ht);
1246 if (num_elems == 0) {
1247 zend_argument_value_error(1, "must have at least one element");
1248 RETURN_THROWS();
1249 }
1250
1251#ifdef PHP_WIN32
1252 /* Automatically bypass shell if command is given as an array */
1253 bypass_shell = 1;
1254 command_str = create_win_command_from_args(command_ht);
1255#else
1256 command_str = get_command_from_array(command_ht, &argv, num_elems);
1257#endif
1258
1259 if (!command_str) {
1260#ifndef PHP_WIN32
1261 efree_argv(argv);
1262#endif
1264 }
1265 } else {
1266 zend_string_addref(command_str);
1267 }
1268
1269#ifdef PHP_WIN32
1270 if (other_options) {
1271 suppress_errors = get_option(other_options, "suppress_errors", strlen("suppress_errors"));
1272 /* TODO: Deprecate in favor of array command? */
1273 bypass_shell = bypass_shell || get_option(other_options, "bypass_shell", strlen("bypass_shell"));
1274 blocking_pipes = get_option(other_options, "blocking_pipes", strlen("blocking_pipes"));
1275 create_process_group = get_option(other_options, "create_process_group", strlen("create_process_group"));
1276 create_new_console = get_option(other_options, "create_new_console", strlen("create_new_console"));
1277 }
1278#endif
1279
1280 if (environment) {
1281 env = _php_array_to_envp(environment);
1282 }
1283
1284 descriptors = alloc_descriptor_array(descriptorspec);
1285
1286 /* Walk the descriptor spec and set up files/pipes */
1287 ZEND_HASH_FOREACH_KEY_VAL(descriptorspec, nindex, str_index, descitem) {
1288 if (str_index) {
1289 zend_argument_value_error(2, "must be an integer indexed array");
1290 goto exit_fail;
1291 }
1292
1293 descriptors[ndesc].index = (int)nindex;
1294
1295 ZVAL_DEREF(descitem);
1296 if (Z_TYPE_P(descitem) == IS_RESOURCE) {
1297 if (set_proc_descriptor_from_resource(descitem, &descriptors[ndesc], ndesc) == FAILURE) {
1298 goto exit_fail;
1299 }
1300 } else if (Z_TYPE_P(descitem) == IS_ARRAY) {
1301 if (set_proc_descriptor_from_array(descitem, descriptors, ndesc, (int)nindex,
1302 &pty_master_fd, &pty_slave_fd) == FAILURE) {
1303 goto exit_fail;
1304 }
1305 } else {
1306 zend_argument_value_error(2, "must only contain arrays and streams");
1307 goto exit_fail;
1308 }
1309 ndesc++;
1311
1312#ifdef PHP_WIN32
1313 if (cwd == NULL) {
1314 char *getcwd_result = VCWD_GETCWD(cur_cwd, MAXPATHLEN);
1315 if (!getcwd_result) {
1316 php_error_docref(NULL, E_WARNING, "Cannot get current directory");
1317 goto exit_fail;
1318 }
1319 cwd = cur_cwd;
1320 }
1321 cwdw = php_win32_cp_any_to_w(cwd);
1322 if (!cwdw) {
1323 php_error_docref(NULL, E_WARNING, "CWD conversion failed");
1324 goto exit_fail;
1325 }
1326
1327 init_startup_info(&si, descriptors, ndesc);
1328 init_process_info(&pi);
1329
1330 if (suppress_errors) {
1331 old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX);
1332 }
1333
1334 dwCreateFlags = NORMAL_PRIORITY_CLASS;
1335 if(strcmp(sapi_module.name, "cli") != 0) {
1336 dwCreateFlags |= CREATE_NO_WINDOW;
1337 }
1338 if (create_process_group) {
1339 dwCreateFlags |= CREATE_NEW_PROCESS_GROUP;
1340 }
1341 if (create_new_console) {
1342 dwCreateFlags |= CREATE_NEW_CONSOLE;
1343 }
1344 envpw = php_win32_cp_env_any_to_w(env.envp);
1345 if (envpw) {
1346 dwCreateFlags |= CREATE_UNICODE_ENVIRONMENT;
1347 } else {
1348 if (env.envp) {
1349 php_error_docref(NULL, E_WARNING, "ENV conversion failed");
1350 goto exit_fail;
1351 }
1352 }
1353
1354 cmdw = php_win32_cp_conv_any_to_w(ZSTR_VAL(command_str), ZSTR_LEN(command_str), &cmdw_len);
1355 if (!cmdw) {
1356 php_error_docref(NULL, E_WARNING, "Command conversion failed");
1357 goto exit_fail;
1358 }
1359
1360 if (!bypass_shell) {
1361 if (convert_command_to_use_shell(&cmdw, cmdw_len) == FAILURE) {
1362 goto exit_fail;
1363 }
1364 }
1365 newprocok = CreateProcessW(NULL, cmdw, &php_proc_open_security,
1366 &php_proc_open_security, TRUE, dwCreateFlags, envpw, cwdw, &si, &pi);
1367
1368 if (suppress_errors) {
1369 SetErrorMode(old_error_mode);
1370 }
1371
1372 if (newprocok == FALSE) {
1373 DWORD dw = GetLastError();
1374 close_all_descriptors(descriptors, ndesc);
1375 char *msg = php_win32_error_to_msg(dw);
1376 php_error_docref(NULL, E_WARNING, "CreateProcess failed: %s", msg);
1378 goto exit_fail;
1379 }
1380
1381 childHandle = pi.hProcess;
1382 child = pi.dwProcessId;
1383 CloseHandle(pi.hThread);
1384#elif defined(USE_POSIX_SPAWN)
1385 posix_spawn_file_actions_t factions;
1386 int r;
1387 posix_spawn_file_actions_init(&factions);
1388
1389 if (close_parentends_of_pipes(&factions, descriptors, ndesc) == FAILURE) {
1390 posix_spawn_file_actions_destroy(&factions);
1391 close_all_descriptors(descriptors, ndesc);
1392 goto exit_fail;
1393 }
1394
1395 if (cwd) {
1396 r = posix_spawn_file_actions_addchdir_np(&factions, cwd);
1397 if (r != 0) {
1398 php_error_docref(NULL, E_WARNING, "posix_spawn_file_actions_addchdir_np() failed: %s", strerror(r));
1399 }
1400 }
1401
1402 if (argv) {
1403 r = posix_spawnp(&child, ZSTR_VAL(command_str), &factions, NULL, argv, (env.envarray ? env.envarray : environ));
1404 } else {
1405 r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
1406 (char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
1407 env.envarray ? env.envarray : environ);
1408 }
1409 posix_spawn_file_actions_destroy(&factions);
1410 if (r != 0) {
1411 close_all_descriptors(descriptors, ndesc);
1412 php_error_docref(NULL, E_WARNING, "posix_spawn() failed: %s", strerror(r));
1413 goto exit_fail;
1414 }
1415#elif defined(HAVE_FORK)
1416 /* the Unix way */
1417 child = fork();
1418
1419 if (child == 0) {
1420 /* This is the child process */
1421
1422 if (close_parentends_of_pipes(descriptors, ndesc) == FAILURE) {
1423 /* We are already in child process and can't do anything to make
1424 * `proc_open` return an error in the parent
1425 * All we can do is exit with a non-zero (error) exit code */
1426 _exit(127);
1427 }
1428
1429 if (cwd) {
1431 }
1432
1433 if (argv) {
1434 /* execvpe() is non-portable, use environ instead. */
1435 if (env.envarray) {
1436 environ = env.envarray;
1437 }
1438 execvp(ZSTR_VAL(command_str), argv);
1439 } else {
1440 if (env.envarray) {
1441 execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
1442 } else {
1443 execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
1444 }
1445 }
1446
1447 /* If execvp/execle/execl are successful, we will never reach here
1448 * Display error and exit with non-zero (error) status code */
1449 php_error_docref(NULL, E_WARNING, "Exec failed: %s", strerror(errno));
1450 _exit(127);
1451 } else if (child < 0) {
1452 /* Failed to fork() */
1453 close_all_descriptors(descriptors, ndesc);
1454 php_error_docref(NULL, E_WARNING, "Fork failed: %s", strerror(errno));
1455 goto exit_fail;
1456 }
1457#else
1458# error You lose (configure should not have let you get here)
1459#endif
1460
1461 /* We forked/spawned and this is the parent */
1462
1463 pipes = zend_try_array_init(pipes);
1464 if (!pipes) {
1465 goto exit_fail;
1466 }
1467
1469 proc->command = zend_string_copy(command_str);
1470 proc->pipes = emalloc(sizeof(zend_resource *) * ndesc);
1471 proc->npipes = ndesc;
1472 proc->child = child;
1473#ifdef PHP_WIN32
1474 proc->childHandle = childHandle;
1475#endif
1476 proc->env = env;
1477#ifdef HAVE_SYS_WAIT_H
1478 proc->has_cached_exit_wait_status = false;
1479#endif
1480
1481 /* Clean up all the child ends and then open streams on the parent
1482 * ends, where appropriate */
1483 for (i = 0; i < ndesc; i++) {
1484 php_stream *stream = NULL;
1485
1486 close_descriptor(descriptors[i].childend);
1487
1488 if (descriptors[i].type == DESCRIPTOR_TYPE_PIPE) {
1489 char *mode_string = NULL;
1490
1491 switch (descriptors[i].mode_flags) {
1492#ifdef PHP_WIN32
1493 case O_WRONLY|O_BINARY:
1494 mode_string = "wb";
1495 break;
1496 case O_RDONLY|O_BINARY:
1497 mode_string = "rb";
1498 break;
1499#endif
1500 case O_WRONLY:
1501 mode_string = "w";
1502 break;
1503 case O_RDONLY:
1504 mode_string = "r";
1505 break;
1506 case O_RDWR:
1507 mode_string = "r+";
1508 break;
1509 }
1510
1511#ifdef PHP_WIN32
1512 stream = php_stream_fopen_from_fd(_open_osfhandle((intptr_t)descriptors[i].parentend,
1513 descriptors[i].mode_flags), mode_string, NULL);
1515#else
1516 stream = php_stream_fopen_from_fd(descriptors[i].parentend, mode_string, NULL);
1517#endif
1518 } else if (descriptors[i].type == DESCRIPTOR_TYPE_SOCKET) {
1519 stream = php_stream_sock_open_from_socket((php_socket_t) descriptors[i].parentend, NULL);
1520 } else {
1521 proc->pipes[i] = NULL;
1522 }
1523
1524 if (stream) {
1525 zval retfp;
1526
1527 /* nasty hack; don't copy it */
1528 stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
1529
1530 php_stream_to_zval(stream, &retfp);
1531 add_index_zval(pipes, descriptors[i].index, &retfp);
1532
1533 proc->pipes[i] = Z_RES(retfp);
1534 Z_ADDREF(retfp);
1535 }
1536 }
1537
1538 if (1) {
1539 RETVAL_RES(zend_register_resource(proc, le_proc_open));
1540 } else {
1541exit_fail:
1542 _php_free_envp(env);
1544 }
1545
1546 zend_string_release_ex(command_str, false);
1547#ifdef PHP_WIN32
1548 free(cwdw);
1549 free(cmdw);
1550 free(envpw);
1551#else
1552 efree_argv(argv);
1553#endif
1554#ifdef HAVE_OPENPTY
1555 if (pty_master_fd != -1) {
1556 close(pty_master_fd);
1557 }
1558 if (pty_slave_fd != -1) {
1559 close(pty_slave_fd);
1560 }
1561#endif
1562 if (descriptors) {
1563 efree(descriptors);
1564 }
1565}
1566/* }}} */
1567
1568#endif /* PHP_CAN_SUPPORT_PROC_OPEN */
SAPI_API sapi_module_struct sapi_module
Definition SAPI.c:65
size_t len
Definition apprentice.c:174
PHPAPI zend_string * php_getenv(const char *str, size_t str_len)
proc_open(array|string $command, array $descriptor_spec, &$pipes, ?string $cwd=null, ?array $env_vars=null, ?array $options=null)
proc_get_status($process)
sizeof(Countable|array $value, int $mode=COUNT_NORMAL)
chdir(string $directory)
proc_terminate($process, int $signal=15)
copy(string $from, string $to, $context=null)
proc_close($process)
assert(mixed $assertion, Throwable|string|null $description=null)
strchr(string $haystack, string $needle, bool $before_needle=false)
PW32CP wchar_t * php_win32_cp_env_any_to_w(const char *env)
Definition codepage.c:428
#define php_win32_cp_any_to_w(in)
Definition codepage.h:115
DNS_STATUS status
Definition dns_win32.c:49
#define DWORD
Definition exif.c:1762
zend_ffi_type * type
Definition ffi.c:3812
zval * zv
Definition ffi.c:3975
char * err
Definition ffi.c:3029
zval * arg
Definition ffi.c:3975
memset(ptr, 0, type->size)
zend_object * ztype
Definition ffi.c:3970
#define O_BINARY
Definition file.h:643
#define FD_CLOEXEC
Definition file.h:148
#define TRUE
Definition gd_gd.c:7
#define FALSE
Definition gd_gd.c:8
#define NULL
Definition gdcache.h:45
#define SUCCESS
Definition hash_sha3.c:261
foreach($dp as $el) foreach( $dp as $el) if( $pass2< 2) echo ""
PHPAPI ZEND_COLD void php_error_docref(const char *docref, int type, const char *format,...)
Definition main.c:1173
PHPAPI zend_string * php_socket_error_str(long err)
Definition network.c:1079
const SIGTERM
const WNOHANG
const WUNTRACED
int BOOL
char * cwd
uint32_t cwd_len
#define PHP_FUNCTION
Definition php.h:364
#define PHP_MINIT_FUNCTION
Definition php.h:400
char ** environ
#define php_ignore_value(x)
Definition php.h:275
PHP_JSON_API size_t int options
Definition php_json.h:102
#define php_stream_sock_open_from_socket(socket, persistent)
#define php_socket_errno()
Definition php_network.h:60
int php_socket_t
php_output_handler * running
Definition php_output.h:141
unsigned char key[REFLECTION_KEY_LEN]
#define php_stream_fopen_from_fd(fd, mode, persistent_id)
#define php_stream_cast(stream, as, ret, show_err)
struct _php_stream php_stream
Definition php_streams.h:96
#define REPORT_ERRORS
#define PHP_STREAM_CAST_RELEASE
#define PHP_STREAM_FLAG_NO_SEEK
#define php_stream_to_zval(stream, zval)
#define STREAM_WILL_CAST
#define php_stream_open_wrapper(path, mode, options, opened)
#define php_stream_set_option(stream, option, value, ptrvalue)
#define PHP_STREAM_AS_FD
#define PHP_STREAM_OPTION_PIPE_BLOCKING
PHPAPI int php_file_le_stream(void)
Definition streams.c:42
char * msg
Definition phpdbg.h:289
int fd
Definition phpdbg.h:282
int php_file_descriptor_t
Definition proc_open.h:22
pid_t php_process_id_t
Definition proc_open.h:23
struct _php_process_env php_process_env
#define PHP_INVALID_FD
Definition proc_open.h:24
struct _php_process_handle php_process_handle
bool fail
Definition session.c:1065
p
Definition session.c:1105
#define FG(v)
Definition file.h:117
char ** envarray
Definition proc_open.h:33
php_process_id_t child
Definition proc_open.h:38
zend_resource ** pipes
Definition proc_open.h:43
zend_string * command
Definition proc_open.h:44
php_process_env env
Definition proc_open.h:45
uint32_t flags
zend_string * s
#define close(a)
#define errno
PHP_WINUTIL_API char * php_win32_error_to_msg(HRESULT error)
Definition winutil.c:25
PHP_WINUTIL_API void php_win32_error_msg_free(char *msg)
Definition winutil.c:50
ZEND_API ZEND_COLD void zend_value_error(const char *format,...)
Definition zend.c:1849
ZEND_API const char * zend_zval_value_name(const zval *arg)
Definition zend_API.c:148
ZEND_API ZEND_COLD void zend_argument_value_error(uint32_t arg_num, const char *format,...)
Definition zend_API.c:433
#define Z_PARAM_ARRAY_OR_NULL(dest)
Definition zend_API.h:1685
#define RETVAL_RES(r)
Definition zend_API.h:1023
#define ZEND_PARSE_PARAMETERS_END()
Definition zend_API.h:1641
#define RETURN_FALSE
Definition zend_API.h:1058
#define Z_PARAM_RESOURCE(dest)
Definition zend_API.h:2056
#define Z_PARAM_OPTIONAL
Definition zend_API.h:1667
#define Z_PARAM_STRING_OR_NULL(dest, dest_len)
Definition zend_API.h:2074
#define ZEND_PARSE_PARAMETERS_START(min_num_args, max_num_args)
Definition zend_API.h:1620
#define Z_PARAM_ARRAY_HT_OR_STR(dest_ht, dest_str)
Definition zend_API.h:2151
#define Z_PARAM_LONG(dest)
Definition zend_API.h:1896
#define RETURN_LONG(l)
Definition zend_API.h:1037
#define RETURN_BOOL(b)
Definition zend_API.h:1035
#define RETURN_THROWS()
Definition zend_API.h:1060
#define Z_PARAM_ARRAY_HT(dest)
Definition zend_API.h:1852
#define Z_PARAM_ZVAL(dest)
Definition zend_API.h:2100
#define RETVAL_FALSE
Definition zend_API.h:1032
#define array_init(arg)
Definition zend_API.h:537
#define ecalloc(nmemb, size)
Definition zend_alloc.h:158
#define efree(ptr)
Definition zend_alloc.h:155
#define estrdup(s)
Definition zend_alloc.h:164
#define FREE_HASHTABLE(ht)
Definition zend_alloc.h:234
#define erealloc(ptr, size)
Definition zend_alloc.h:159
#define safe_emalloc(nmemb, size, offset)
Definition zend_alloc.h:154
#define ALLOC_HASHTABLE(ht)
Definition zend_alloc.h:231
#define emalloc(size)
Definition zend_alloc.h:151
struct _zval_struct zval
strlen(string $string)
strcmp(string $string1, string $string2)
zend_string_release_ex(func->internal_function.function_name, 0)
zval * args
#define E_WARNING
Definition zend_errors.h:24
ZEND_API void ZEND_FASTCALL zend_hash_destroy(HashTable *ht)
Definition zend_hash.c:1727
ZEND_API zval *ZEND_FASTCALL zend_hash_index_find(const HashTable *ht, zend_ulong h)
Definition zend_hash.c:2701
#define zend_hash_init(ht, nSize, pHashFunction, pDestructor, persistent)
Definition zend_hash.h:108
#define ZEND_HASH_FOREACH_STR_KEY_PTR(ht, _key, _ptr)
Definition zend_hash.h:1225
#define ZEND_HASH_FOREACH_KEY_VAL(ht, _h, _key, _val)
Definition zend_hash.h:1181
#define ZEND_HASH_FOREACH_STR_KEY_VAL(ht, _key, _val)
Definition zend_hash.h:1166
#define ZEND_HASH_FOREACH_END()
Definition zend_hash.h:1086
#define ZEND_HASH_FOREACH_VAL(ht, _val)
Definition zend_hash.h:1102
ZEND_API void * zend_fetch_resource(zend_resource *res, const char *resource_type_name, int resource_type)
Definition zend_list.c:117
ZEND_API zend_resource * zend_register_resource(void *rsrc_pointer, int rsrc_type)
Definition zend_list.c:87
ZEND_API void ZEND_FASTCALL zend_list_close(zend_resource *res)
Definition zend_list.c:78
ZEND_API int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, const char *type_name, int module_number)
Definition zend_list.c:265
int32_t zend_long
Definition zend_long.h:42
uint32_t zend_ulong
Definition zend_long.h:43
#define ZEND_LONG_FMT
Definition zend_long.h:87
struct _zend_string zend_string
#define EMPTY_SWITCH_DEFAULT_CASE()
#define ZSTR_VAL(zstr)
Definition zend_string.h:68
#define ZSTR_KNOWN(idx)
#define zend_string_equals_literal(str, literal)
#define ZSTR_LEN(zstr)
Definition zend_string.h:69
#define zend_string_starts_with_literal(str, prefix)
#define Z_TYPE_P(zval_p)
Definition zend_types.h:660
#define IS_TRUE
Definition zend_types.h:603
#define Z_ARRVAL_P(zval_p)
Definition zend_types.h:987
#define Z_RES(zval)
#define ZVAL_DEREF(z)
struct _zend_resource zend_resource
Definition zend_types.h:99
struct _zend_array HashTable
Definition zend_types.h:386
#define IS_RESOURCE
Definition zend_types.h:609
#define IS_ARRAY
Definition zend_types.h:607
#define GC_DELREF(p)
Definition zend_types.h:710
@ FAILURE
Definition zend_types.h:61
#define Z_ADDREF(z)
#define IS_LONG
Definition zend_types.h:604
ZEND_RESULT_CODE zend_result
Definition zend_types.h:64
#define Z_RES_P(zval_p)
#define Z_LVAL_P(zval_p)
Definition zend_types.h:966
#define VCWD_GETCWD(buff, size)
#define MAXPATHLEN
zval retval
zval * return_value
zend_string * name
out($f, $s)