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 #include "thirdparty/php/standard/proc_open.h"
20 #include "swoole_coroutine_c_api.h"
21 
22 using namespace std;
23 using swoole::Coroutine;
24 using swoole::PHPCoroutine;
25 using swoole::coroutine::Socket;
26 
27 #if HAVE_SYS_STAT_H
28 #include <sys/stat.h>
29 #endif
30 #if HAVE_FCNTL_H
31 #include <fcntl.h>
32 #endif
33 
34 static int le_proc_open;
35 static const char *le_proc_name = "process/coroutine";
36 
37 /* {{{ _php_array_to_envp */
_php_array_to_envp(zval * environment)38 static proc_co_env_t _php_array_to_envp(zval *environment) {
39     zval *element;
40     proc_co_env_t env;
41     zend_string *key, *str;
42     char **ep;
43     char *p;
44     size_t cnt, sizeenv = 0;
45     HashTable *env_hash;
46 
47     memset(&env, 0, sizeof(env));
48 
49     if (!environment) {
50         return env;
51     }
52 
53     cnt = zend_hash_num_elements(Z_ARRVAL_P(environment));
54 
55     if (cnt < 1) {
56         env.envarray = (char **) ecalloc(1, sizeof(char *));
57         env.envp = (char *) ecalloc(4, 1);
58         return env;
59     }
60 
61     ALLOC_HASHTABLE(env_hash);
62     zend_hash_init(env_hash, cnt, NULL, NULL, 0);
63 
64     /* first, we have to get the size of all the elements in the hash */
65     ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(environment), key, element) {
66         str = zval_get_string(element);
67 
68         if (ZSTR_LEN(str) == 0) {
69             zend_string_release(str);
70             continue;
71         }
72 
73         sizeenv += ZSTR_LEN(str) + 1;
74 
75         if (key && ZSTR_LEN(key)) {
76             sizeenv += ZSTR_LEN(key) + 1;
77             zend_hash_add_ptr(env_hash, key, str);
78         } else {
79             zend_hash_next_index_insert_ptr(env_hash, str);
80         }
81     }
82     ZEND_HASH_FOREACH_END();
83 
84     ep = env.envarray = (char **) ecalloc(cnt + 1, sizeof(char *));
85     p = env.envp = (char *) ecalloc(sizeenv + 4, 1);
86 
87     void *v1, *v2;
88     ZEND_HASH_FOREACH_STR_KEY_PTR(env_hash, v1, v2) {
89         key = (zend_string *) v1;
90         str = (zend_string *) v2;
91         *ep = p;
92         ++ep;
93 
94         if (key) {
95             memcpy(p, ZSTR_VAL(key), ZSTR_LEN(key));
96             p += ZSTR_LEN(key);
97             *p++ = '=';
98         }
99 
100         memcpy(p, ZSTR_VAL(str), ZSTR_LEN(str));
101         p += ZSTR_LEN(str);
102         *p++ = '\0';
103         zend_string_release(str);
104     }
105     ZEND_HASH_FOREACH_END();
106 
107     assert((uint32_t)(p - env.envp) <= sizeenv);
108 
109     zend_hash_destroy(env_hash);
110     FREE_HASHTABLE(env_hash);
111 
112     return env;
113 }
114 /* }}} */
115 
116 /* {{{ _php_free_envp */
_php_free_envp(proc_co_env_t env,int is_persistent)117 static void _php_free_envp(proc_co_env_t env, int is_persistent) {
118     if (env.envarray) {
119         pefree(env.envarray, is_persistent);
120     }
121     if (env.envp) {
122         pefree(env.envp, is_persistent);
123     }
124 }
125 /* }}} */
126 
proc_co_rsrc_dtor(zend_resource * rsrc)127 static void proc_co_rsrc_dtor(zend_resource *rsrc) {
128     proc_co_t *proc = (proc_co_t *) rsrc->ptr;
129     int i;
130     int wstatus = 0;
131 
132     /* Close all handles to avoid a deadlock */
133     for (i = 0; i < proc->npipes; i++) {
134         if (proc->pipes[i] != 0) {
135             GC_DELREF(proc->pipes[i]);
136             zend_list_close(proc->pipes[i]);
137             proc->pipes[i] = 0;
138         }
139     }
140 
141     if (proc->running) {
142         if (::waitpid(proc->child, &wstatus, WNOHANG) == 0) {
143             swoole_coroutine_waitpid(proc->child, &wstatus, 0);
144         }
145     }
146     if (proc->wstatus) {
147         *proc->wstatus = wstatus;
148     }
149 
150     _php_free_envp(proc->env, proc->is_persistent);
151     efree(proc->pipes);
152     efree(proc->command);
153     efree(proc);
154 }
155 
swoole_proc_open_init(int module_number)156 void swoole_proc_open_init(int module_number) {
157     le_proc_open = zend_register_list_destructors_ex(proc_co_rsrc_dtor, NULL, le_proc_name, module_number);
158 }
159 
160 /* {{{ proto bool proc_terminate(resource process [, int signal])
161    kill a process opened by proc_open */
PHP_FUNCTION(swoole_proc_terminate)162 PHP_FUNCTION(swoole_proc_terminate) {
163     zval *zproc;
164     proc_co_t *proc;
165     zend_long sig_no = SIGTERM;
166 
167     ZEND_PARSE_PARAMETERS_START(1, 2)
168     Z_PARAM_RESOURCE(zproc)
169     Z_PARAM_OPTIONAL
170     Z_PARAM_LONG(sig_no)
171     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
172 
173     if ((proc = (proc_co_t *) zend_fetch_resource(Z_RES_P(zproc), le_proc_name, le_proc_open)) == NULL) {
174         RETURN_FALSE;
175     }
176 
177     RETURN_BOOL(kill(proc->child, sig_no) == 0);
178 }
179 /* }}} */
180 
PHP_FUNCTION(swoole_proc_close)181 PHP_FUNCTION(swoole_proc_close) {
182     zval *zproc;
183     proc_co_t *proc;
184     int wstatus;
185 
186     ZEND_PARSE_PARAMETERS_START(1, 1)
187     Z_PARAM_RESOURCE(zproc)
188     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
189 
190     if ((proc = (proc_co_t *) zend_fetch_resource(Z_RES_P(zproc), le_proc_name, le_proc_open)) == NULL) {
191         RETURN_FALSE;
192     }
193 
194     proc->wstatus = &wstatus;
195     zend_list_close(Z_RES_P(zproc));
196     RETURN_LONG(wstatus);
197 }
198 
PHP_FUNCTION(swoole_proc_get_status)199 PHP_FUNCTION(swoole_proc_get_status) {
200     zval *zproc;
201     proc_co_t *proc;
202     int wstatus;
203     pid_t wait_pid;
204     int running = 1, signaled = 0, stopped = 0;
205     int exitcode = -1, termsig = 0, stopsig = 0;
206 
207     ZEND_PARSE_PARAMETERS_START(1, 1)
208     Z_PARAM_RESOURCE(zproc)
209     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
210 
211     if ((proc = (proc_co_t *) zend_fetch_resource(Z_RES_P(zproc), le_proc_name, le_proc_open)) == NULL) {
212         RETURN_FALSE;
213     }
214 
215     array_init(return_value);
216 
217     add_assoc_string(return_value, "command", proc->command);
218     add_assoc_long(return_value, "pid", (zend_long) proc->child);
219 
220     errno = 0;
221     wait_pid = swoole_coroutine_waitpid(proc->child, &wstatus, WNOHANG | WUNTRACED);
222 
223     if (wait_pid == proc->child) {
224         if (WIFEXITED(wstatus)) {
225             running = 0;
226             exitcode = WEXITSTATUS(wstatus);
227         }
228         if (WIFSIGNALED(wstatus)) {
229             running = 0;
230             signaled = 1;
231 
232             termsig = WTERMSIG(wstatus);
233         }
234         if (WIFSTOPPED(wstatus)) {
235             stopped = 1;
236             stopsig = WSTOPSIG(wstatus);
237         }
238     } else if (wait_pid == -1) {
239         running = 0;
240     }
241 
242     proc->running = running;
243 
244     add_assoc_bool(return_value, "running", running);
245     add_assoc_bool(return_value, "signaled", signaled);
246     add_assoc_bool(return_value, "stopped", stopped);
247     add_assoc_long(return_value, "exitcode", exitcode);
248     add_assoc_long(return_value, "termsig", termsig);
249     add_assoc_long(return_value, "stopsig", stopsig);
250 }
251 /* }}} */
252 
253 #define DESC_PIPE 1
254 #define DESC_FILE 2
255 #define DESC_REDIRECT 3
256 #define DESC_PARENT_MODE_WRITE 8
257 
258 struct php_proc_open_descriptor_item {
259     int index;               /* desired fd number in child process */
260     int parentend, childend; /* fds for pipes in parent/child */
261     int mode;                /* mode for proc_open code */
262     int mode_flags;          /* mode flags for opening fds */
263 };
264 /* }}} */
265 
get_valid_arg_string(zval * zv,int elem_num)266 static zend_string *get_valid_arg_string(zval *zv, int elem_num) {
267     zend_string *str = zval_get_string(zv);
268     if (!str) {
269         return NULL;
270     }
271 
272     if (strlen(ZSTR_VAL(str)) != ZSTR_LEN(str)) {
273         php_error_docref(NULL, E_WARNING, "Command array element %d contains a null byte", elem_num);
274         zend_string_release(str);
275         return NULL;
276     }
277 
278     return str;
279 }
280 
281 /* {{{ proto resource proc_open(string|array command, array descriptorspec, array &pipes [, string cwd [, array env [,
282    array other_options]]]) Run a process with more control over it's file descriptors */
PHP_FUNCTION(swoole_proc_open)283 PHP_FUNCTION(swoole_proc_open) {
284     zval *command_zv;
285     char *command = NULL, *cwd = NULL;
286     size_t cwd_len = 0;
287     zval *descriptorspec;
288     zval *pipes;
289     zval *environment = NULL;
290     zval *other_options = NULL;
291     proc_co_env_t env;
292     int ndesc = 0;
293     int i;
294     zval *descitem = NULL;
295     zend_string *str_index;
296     zend_ulong nindex;
297     struct php_proc_open_descriptor_item *descriptors = NULL;
298     int ndescriptors_array;
299 
300     char **argv = NULL;
301 
302     pid_t child;
303     proc_co_t *proc;
304     int is_persistent = 0; /* TODO: ensure that persistent procs will work */
305 
306     ZEND_PARSE_PARAMETERS_START(3, 6)
307     Z_PARAM_ZVAL(command_zv)
308     Z_PARAM_ARRAY(descriptorspec)
309 #if PHP_VERSION_ID >= 70400
310     Z_PARAM_ZVAL(pipes)
311 #else
312     Z_PARAM_ZVAL_DEREF(pipes)
313 #endif
314     Z_PARAM_OPTIONAL
315     Z_PARAM_STRING_EX(cwd, cwd_len, 1, 0)
316     Z_PARAM_ARRAY_EX(environment, 1, 0)
317     Z_PARAM_ARRAY_EX(other_options, 1, 0)
318     ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
319 
320     memset(&env, 0, sizeof(env));
321 
322     if (Z_TYPE_P(command_zv) == IS_ARRAY) {
323         zval *arg_zv;
324         uint32_t num_elems = zend_hash_num_elements(Z_ARRVAL_P(command_zv));
325         if (num_elems == 0) {
326             php_error_docref(NULL, E_WARNING, "Command array must have at least one element");
327             RETURN_FALSE;
328         }
329 
330         argv = (char **) safe_emalloc(sizeof(char *), num_elems + 1, 0);
331         i = 0;
332         ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(command_zv), arg_zv) {
333             zend_string *arg_str = get_valid_arg_string(arg_zv, i + 1);
334             if (!arg_str) {
335                 argv[i] = NULL;
336                 goto exit_fail;
337             }
338 
339             if (i == 0) {
340                 command = pestrdup(ZSTR_VAL(arg_str), is_persistent);
341             }
342 
343             argv[i++] = estrdup(ZSTR_VAL(arg_str));
344             zend_string_release(arg_str);
345         }
346         ZEND_HASH_FOREACH_END();
347         argv[i] = NULL;
348 
349         /* As the array is non-empty, we should have found a command. */
350         ZEND_ASSERT(command);
351     } else {
352         convert_to_string(command_zv);
353         command = pestrdup(Z_STRVAL_P(command_zv), is_persistent);
354     }
355 
356     php_swoole_check_reactor();
357     if (php_swoole_signal_isset_handler(SIGCHLD)) {
358         php_swoole_error(E_WARNING, "The signal [SIGCHLD] is registered, cannot execute swoole_proc_open");
359         RETURN_FALSE;
360     }
361 
362     Coroutine::get_current_safe();
363 
364     if (environment) {
365         env = _php_array_to_envp(environment);
366     } else {
367         memset(&env, 0, sizeof(env));
368     }
369 
370     ndescriptors_array = zend_hash_num_elements(Z_ARRVAL_P(descriptorspec));
371 
372     descriptors = (struct php_proc_open_descriptor_item *) safe_emalloc(
373         sizeof(struct php_proc_open_descriptor_item), ndescriptors_array, 0);
374 
375     memset(descriptors, 0, sizeof(struct php_proc_open_descriptor_item) * ndescriptors_array);
376 
377     /* walk the descriptor spec and set up files/pipes */
378     ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(descriptorspec), nindex, str_index, descitem) {
379         zval *ztype;
380 
381         if (str_index) {
382             php_swoole_fatal_error(E_WARNING, "descriptor spec must be an integer indexed array");
383             goto exit_fail;
384         }
385 
386         descriptors[ndesc].index = (int) nindex;
387 
388         if (Z_TYPE_P(descitem) == IS_RESOURCE) {
389             /* should be a stream - try and dup the descriptor */
390             php_stream *stream;
391             php_socket_t fd;
392 
393             php_stream_from_zval(stream, descitem);
394 
395             if (FAILURE == php_stream_cast(stream, PHP_STREAM_AS_FD, (void **) &fd, REPORT_ERRORS)) {
396                 goto exit_fail;
397             }
398 
399             descriptors[ndesc].childend = dup(fd);
400             if (descriptors[ndesc].childend < 0) {
401                 php_swoole_fatal_error(E_WARNING,
402                                        "unable to dup File-Handle for descriptor " ZEND_ULONG_FMT " - %s",
403                                        nindex,
404                                        strerror(errno));
405                 goto exit_fail;
406             }
407 
408             descriptors[ndesc].mode = DESC_FILE;
409 
410         } else if (Z_TYPE_P(descitem) != IS_ARRAY) {
411             php_swoole_fatal_error(E_WARNING, "Descriptor item must be either an array or a File-Handle");
412             goto exit_fail;
413         } else {
414             if ((ztype = zend_hash_index_find(Z_ARRVAL_P(descitem), 0)) != NULL) {
415                 convert_to_string_ex(ztype);
416             } else {
417                 php_swoole_fatal_error(E_WARNING, "Missing handle qualifier in array");
418                 goto exit_fail;
419             }
420 
421             if (strcmp(Z_STRVAL_P(ztype), "pipe") == 0) {
422                 php_file_descriptor_t newpipe[2];
423                 zval *zmode;
424 
425                 if ((zmode = zend_hash_index_find(Z_ARRVAL_P(descitem), 1)) != NULL) {
426                     convert_to_string_ex(zmode);
427                 } else {
428                     php_swoole_fatal_error(E_WARNING, "Missing mode parameter for 'pipe'");
429                     goto exit_fail;
430                 }
431 
432                 descriptors[ndesc].mode = DESC_PIPE;
433 
434                 if (0 != socketpair(AF_UNIX, SOCK_STREAM, 0, newpipe)) {
435                     php_swoole_fatal_error(E_WARNING, "unable to create pipe %s", strerror(errno));
436                     goto exit_fail;
437                 }
438 
439                 if (strncmp(Z_STRVAL_P(zmode), "w", 1) != 0) {
440                     descriptors[ndesc].parentend = newpipe[1];
441                     descriptors[ndesc].childend = newpipe[0];
442                     descriptors[ndesc].mode |= DESC_PARENT_MODE_WRITE;
443                 } else {
444                     descriptors[ndesc].parentend = newpipe[0];
445                     descriptors[ndesc].childend = newpipe[1];
446                 }
447                 descriptors[ndesc].mode_flags = descriptors[ndesc].mode & DESC_PARENT_MODE_WRITE ? O_WRONLY : O_RDONLY;
448 
449             } else if (strcmp(Z_STRVAL_P(ztype), "file") == 0) {
450                 zval *zfile, *zmode;
451                 php_socket_t fd;
452                 php_stream *stream;
453 
454                 descriptors[ndesc].mode = DESC_FILE;
455 
456                 if ((zfile = zend_hash_index_find(Z_ARRVAL_P(descitem), 1)) != NULL) {
457 #if PHP_VERSION_ID >= 70400
458                     if (!try_convert_to_string(zfile)) {
459                         goto exit_fail;
460                     }
461 #else
462                     convert_to_string_ex(zfile);
463 #endif
464                 } else {
465                     php_swoole_fatal_error(E_WARNING, "Missing file name parameter for 'file'");
466                     goto exit_fail;
467                 }
468 
469                 if ((zmode = zend_hash_index_find(Z_ARRVAL_P(descitem), 2)) != NULL) {
470 #if PHP_VERSION_ID >= 70400
471                     if (!try_convert_to_string(zmode)) {
472                         goto exit_fail;
473                     }
474 #else
475                     convert_to_string_ex(zmode);
476 #endif
477                 } else {
478                     php_swoole_fatal_error(E_WARNING, "Missing mode parameter for 'file'");
479                     goto exit_fail;
480                 }
481 
482                 /* try a wrapper */
483                 stream = php_stream_open_wrapper(
484                     Z_STRVAL_P(zfile), Z_STRVAL_P(zmode), REPORT_ERRORS | STREAM_WILL_CAST, NULL);
485 
486                 /* force into an fd */
487                 if (stream == NULL ||
488                     FAILURE == php_stream_cast(
489                                    stream, PHP_STREAM_CAST_RELEASE | PHP_STREAM_AS_FD, (void **) &fd, REPORT_ERRORS)) {
490                     goto exit_fail;
491                 }
492 
493                 descriptors[ndesc].childend = fd;
494             } else if (strcmp(Z_STRVAL_P(ztype), "redirect") == 0) {
495                 zval *ztarget = zend_hash_index_find_deref(Z_ARRVAL_P(descitem), 1);
496                 struct php_proc_open_descriptor_item *target = NULL;
497                 php_file_descriptor_t childend;
498 
499                 if (!ztarget) {
500                     php_error_docref(NULL, E_WARNING, "Missing redirection target");
501                     goto exit_fail;
502                 }
503                 if (Z_TYPE_P(ztarget) != IS_LONG) {
504                     php_error_docref(NULL, E_WARNING, "Redirection target must be an integer");
505                     goto exit_fail;
506                 }
507 
508                 for (i = 0; i < ndesc; i++) {
509                     if (descriptors[i].index == Z_LVAL_P(ztarget)) {
510                         target = &descriptors[i];
511                         break;
512                     }
513                 }
514                 if (target) {
515                     childend = target->childend;
516                 } else {
517                     if (Z_LVAL_P(ztarget) < 0 || Z_LVAL_P(ztarget) > 2) {
518                         php_error_docref(
519                             NULL, E_WARNING, "Redirection target " ZEND_LONG_FMT " not found", Z_LVAL_P(ztarget));
520                         goto exit_fail;
521                     }
522 
523                     /* Support referring to a stdin/stdout/stderr pipe adopted from the parent,
524                      * which happens whenever an explicit override is not provided. */
525 #ifndef PHP_WIN32
526                     childend = Z_LVAL_P(ztarget);
527 #else
528                     switch (Z_LVAL_P(ztarget)) {
529                     case 0:
530                         childend = GetStdHandle(STD_INPUT_HANDLE);
531                         break;
532                     case 1:
533                         childend = GetStdHandle(STD_OUTPUT_HANDLE);
534                         break;
535                     case 2:
536                         childend = GetStdHandle(STD_ERROR_HANDLE);
537                         break;
538                         EMPTY_SWITCH_DEFAULT_CASE()
539                     }
540 #endif
541                 }
542 
543 #ifdef PHP_WIN32
544                 descriptors[ndesc].childend = dup_handle(childend, TRUE, FALSE);
545                 if (descriptors[ndesc].childend == NULL) {
546                     php_error_docref(NULL, E_WARNING, "Failed to dup() for descriptor " ZEND_LONG_FMT, nindex);
547                     goto exit_fail;
548                 }
549 #else
550                 descriptors[ndesc].childend = dup(childend);
551                 if (descriptors[ndesc].childend < 0) {
552                     php_error_docref(NULL,
553                                      E_WARNING,
554                                      "Failed to dup() for descriptor " ZEND_LONG_FMT " - %s",
555                                      nindex,
556                                      strerror(errno));
557                     goto exit_fail;
558                 }
559 #endif
560                 descriptors[ndesc].mode = DESC_REDIRECT;
561             } else if (strcmp(Z_STRVAL_P(ztype), "null") == 0) {
562 #ifndef PHP_WIN32
563                 descriptors[ndesc].childend = open("/dev/null", O_RDWR);
564                 if (descriptors[ndesc].childend < 0) {
565                     php_error_docref(NULL, E_WARNING, "Failed to open /dev/null - %s", strerror(errno));
566                     goto exit_fail;
567                 }
568 #else
569                 descriptors[ndesc].childend = CreateFileA("nul",
570                                                           GENERIC_READ | GENERIC_WRITE,
571                                                           FILE_SHARE_READ | FILE_SHARE_WRITE,
572                                                           NULL,
573                                                           OPEN_EXISTING,
574                                                           0,
575                                                           NULL);
576                 if (descriptors[ndesc].childend == NULL) {
577                     php_error_docref(NULL, E_WARNING, "Failed to open nul");
578                     goto exit_fail;
579                 }
580 #endif
581                 descriptors[ndesc].mode = DESC_FILE;
582             } else if (strcmp(Z_STRVAL_P(ztype), "pty") == 0) {
583                 php_swoole_fatal_error(E_WARNING, "pty pseudo terminal not supported on this system");
584                 goto exit_fail;
585             } else {
586                 php_swoole_fatal_error(E_WARNING, "%s is not a valid descriptor spec/mode", Z_STRVAL_P(ztype));
587                 goto exit_fail;
588             }
589         }
590         ndesc++;
591     }
592     ZEND_HASH_FOREACH_END();
593 
594     /* the unix way */
595     child = swoole_fork(SW_FORK_EXEC);
596 
597     if (child == 0) {
598         /* this is the child process */
599 
600         /* close those descriptors that we just opened for the parent stuff,
601          * dup new descriptors into required descriptors and close the original
602          * cruft */
603         for (i = 0; i < ndesc; i++) {
604             switch (descriptors[i].mode & ~DESC_PARENT_MODE_WRITE) {
605             case DESC_PIPE:
606                 close(descriptors[i].parentend);
607                 break;
608             }
609             if (dup2(descriptors[i].childend, descriptors[i].index) < 0) {
610                 perror("dup2");
611             }
612             if (descriptors[i].childend != descriptors[i].index) {
613                 close(descriptors[i].childend);
614             }
615         }
616 
617         if (cwd) {
618             php_ignore_value(chdir(cwd));
619         }
620 
621         if (argv) {
622             /* execvpe() is non-portable, use environ instead. */
623             if (env.envarray) {
624                 environ = env.envarray;
625             }
626             execvp(command, argv);
627         } else {
628             if (env.envarray) {
629                 execle("/bin/sh", "sh", "-c", command, NULL, env.envarray);
630             } else {
631                 execl("/bin/sh", "sh", "-c", command, NULL);
632             }
633         }
634         _exit(127);
635 
636     } else if (child < 0) {
637         /* failed to fork() */
638 
639         /* clean up all the descriptors */
640         for (i = 0; i < ndesc; i++) {
641             close(descriptors[i].childend);
642             if (descriptors[i].parentend) {
643                 close(descriptors[i].parentend);
644             }
645         }
646 
647         php_swoole_fatal_error(E_WARNING, "fork failed - %s", strerror(errno));
648 
649         goto exit_fail;
650     }
651 
652     /* we forked/spawned and this is the parent */
653 
654 #if PHP_VERSION_ID >= 70400
655     pipes = zend_try_array_init(pipes);
656     if (!pipes) {
657         goto exit_fail;
658     }
659 #else
660     zval_ptr_dtor(pipes);
661     array_init(pipes);
662 #endif
663 
664     proc = (proc_co_t *) pemalloc(sizeof(proc_co_t), is_persistent);
665     proc->is_persistent = is_persistent;
666     proc->wstatus = nullptr;
667     proc->running = true;
668     proc->command = command;
669     proc->pipes = (zend_resource **) emalloc(sizeof(zend_resource *) * ndesc);
670     proc->npipes = ndesc;
671     proc->child = child;
672     proc->env = env;
673 
674     /* clean up all the child ends and then open streams on the parent
675      * ends, where appropriate */
676     for (i = 0; i < ndesc; i++) {
677         php_stream *stream = NULL;
678 
679         close(descriptors[i].childend);
680 
681         switch (descriptors[i].mode & ~DESC_PARENT_MODE_WRITE) {
682         case DESC_PIPE:
683             stream = php_swoole_create_stream_from_socket(descriptors[i].parentend, AF_UNIX, SOCK_STREAM, 0 STREAMS_CC);
684             /* mark the descriptor close-on-exec, so that it won't be inherited by potential other children */
685             fcntl(descriptors[i].parentend, F_SETFD, FD_CLOEXEC);
686             if (stream) {
687                 zval retfp;
688 
689                 /* nasty hack; don't copy it */
690                 stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
691 
692                 php_stream_to_zval(stream, &retfp);
693                 (void) add_index_zval(pipes, descriptors[i].index, &retfp);
694 
695                 proc->pipes[i] = Z_RES(retfp);
696                 Z_ADDREF(retfp);
697             }
698             break;
699         default:
700             proc->pipes[i] = NULL;
701         }
702     }
703 
704     if (argv) {
705         char **arg = argv;
706         while (*arg != NULL) {
707             efree(*arg);
708             arg++;
709         }
710         efree(argv);
711     }
712 
713     efree(descriptors);
714     ZVAL_RES(return_value, zend_register_resource(proc, le_proc_open));
715     return;
716 
717 exit_fail:
718     if (descriptors) {
719         efree(descriptors);
720     }
721     _php_free_envp(env, is_persistent);
722     if (command) {
723         pefree(command, is_persistent);
724     }
725     if (argv) {
726         char **arg = argv;
727         while (*arg != NULL) {
728             efree(*arg);
729             arg++;
730         }
731         efree(argv);
732     }
733     RETURN_FALSE;
734 }
735 /* }}} */
736