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