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