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