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