1 | /* |
2 | +----------------------------------------------------------------------+ |
3 | | PHP Version 5 | |
4 | +----------------------------------------------------------------------+ |
5 | | Copyright (c) 1997-2015 The PHP Group | |
6 | +----------------------------------------------------------------------+ |
7 | | This source file is subject to version 3.01 of the PHP license, | |
8 | | that is bundled with this package in the file LICENSE, and is | |
9 | | available through the world-wide-web at the following url: | |
10 | | http://www.php.net/license/3_01.txt | |
11 | | If you did not receive a copy of the PHP license and are unable to | |
12 | | obtain it through the world-wide-web, please send a note to | |
13 | | license@php.net so we can mail you a copy immediately. | |
14 | +----------------------------------------------------------------------+ |
15 | | Author: Wez Furlong <wez@thebrainroom.com> | |
16 | +----------------------------------------------------------------------+ |
17 | */ |
18 | /* $Id$ */ |
19 | |
20 | #if 0 && (defined(__linux__) || defined(sun) || defined(__IRIX__)) |
21 | # define _BSD_SOURCE /* linux wants this when XOPEN mode is on */ |
22 | # define _BSD_COMPAT /* irix: uint */ |
23 | # define _XOPEN_SOURCE 500 /* turn on Unix98 */ |
24 | # define __EXTENSIONS__ 1 /* Solaris: uint */ |
25 | #endif |
26 | |
27 | #include "php.h" |
28 | #include <stdio.h> |
29 | #include <ctype.h> |
30 | #include "php_string.h" |
31 | #include "ext/standard/head.h" |
32 | #include "ext/standard/basic_functions.h" |
33 | #include "ext/standard/file.h" |
34 | #include "exec.h" |
35 | #include "php_globals.h" |
36 | #include "SAPI.h" |
37 | #include "main/php_network.h" |
38 | |
39 | #ifdef NETWARE |
40 | #include <proc.h> |
41 | #include <library.h> |
42 | #endif |
43 | |
44 | #if HAVE_SYS_WAIT_H |
45 | #include <sys/wait.h> |
46 | #endif |
47 | #if HAVE_SIGNAL_H |
48 | #include <signal.h> |
49 | #endif |
50 | |
51 | #if HAVE_SYS_STAT_H |
52 | #include <sys/stat.h> |
53 | #endif |
54 | #if HAVE_FCNTL_H |
55 | #include <fcntl.h> |
56 | #endif |
57 | |
58 | /* This symbol is defined in ext/standard/config.m4. |
59 | * Essentially, it is set if you HAVE_FORK || PHP_WIN32 |
60 | * Other platforms may modify that configure check and add suitable #ifdefs |
61 | * around the alternate code. |
62 | * */ |
63 | #ifdef PHP_CAN_SUPPORT_PROC_OPEN |
64 | |
65 | #if 0 && HAVE_PTSNAME && HAVE_GRANTPT && HAVE_UNLOCKPT && HAVE_SYS_IOCTL_H && HAVE_TERMIOS_H |
66 | # include <sys/ioctl.h> |
67 | # include <termios.h> |
68 | # define PHP_CAN_DO_PTS 1 |
69 | #endif |
70 | |
71 | #include "proc_open.h" |
72 | |
73 | static int le_proc_open; |
74 | |
75 | /* {{{ _php_array_to_envp */ |
76 | static php_process_env_t _php_array_to_envp(zval *environment, int is_persistent TSRMLS_DC) |
77 | { |
78 | zval **element; |
79 | php_process_env_t env; |
80 | char *string_key, *data; |
81 | #ifndef PHP_WIN32 |
82 | char **ep; |
83 | #endif |
84 | char *p; |
85 | uint string_length, cnt, l, sizeenv=0, el_len; |
86 | ulong num_key; |
87 | HashTable *target_hash; |
88 | HashPosition pos; |
89 | |
90 | memset(&env, 0, sizeof(env)); |
91 | |
92 | if (!environment) { |
93 | return env; |
94 | } |
95 | |
96 | cnt = zend_hash_num_elements(Z_ARRVAL_P(environment)); |
97 | |
98 | if (cnt < 1) { |
99 | #ifndef PHP_WIN32 |
100 | env.envarray = (char **) pecalloc(1, sizeof(char *), is_persistent); |
101 | #endif |
102 | env.envp = (char *) pecalloc(4, 1, is_persistent); |
103 | return env; |
104 | } |
105 | |
106 | target_hash = HASH_OF(environment); |
107 | if (!target_hash) { |
108 | return env; |
109 | } |
110 | |
111 | /* first, we have to get the size of all the elements in the hash */ |
112 | for (zend_hash_internal_pointer_reset_ex(target_hash, &pos); |
113 | zend_hash_get_current_data_ex(target_hash, (void **) &element, &pos) == SUCCESS; |
114 | zend_hash_move_forward_ex(target_hash, &pos)) { |
115 | |
116 | if (Z_TYPE_PP(element) != IS_STRING) { |
117 | zval tmp; |
118 | |
119 | MAKE_COPY_ZVAL(element, &tmp); |
120 | convert_to_string(&tmp); |
121 | el_len = Z_STRLEN(tmp); |
122 | |
123 | zval_dtor(&tmp); |
124 | } else { |
125 | el_len = Z_STRLEN_PP(element); |
126 | } |
127 | if (el_len == 0) { |
128 | continue; |
129 | } |
130 | |
131 | sizeenv += el_len+1; |
132 | |
133 | switch (zend_hash_get_current_key_ex(target_hash, &string_key, &string_length, &num_key, 0, &pos)) { |
134 | case HASH_KEY_IS_STRING: |
135 | if (string_length == 0) { |
136 | continue; |
137 | } |
138 | sizeenv += string_length; |
139 | break; |
140 | } |
141 | } |
142 | |
143 | #ifndef PHP_WIN32 |
144 | ep = env.envarray = (char **) pecalloc(cnt + 1, sizeof(char *), is_persistent); |
145 | #endif |
146 | p = env.envp = (char *) pecalloc(sizeenv + 4, 1, is_persistent); |
147 | |
148 | for (zend_hash_internal_pointer_reset_ex(target_hash, &pos); |
149 | zend_hash_get_current_data_ex(target_hash, (void **) &element, &pos) == SUCCESS; |
150 | zend_hash_move_forward_ex(target_hash, &pos)) { |
151 | zval tmp; |
152 | |
153 | if (Z_TYPE_PP(element) != IS_STRING) { |
154 | MAKE_COPY_ZVAL(element, &tmp); |
155 | convert_to_string(&tmp); |
156 | } else { |
157 | tmp = **element; |
158 | } |
159 | |
160 | el_len = Z_STRLEN(tmp); |
161 | |
162 | if (el_len == 0) { |
163 | goto next_element; |
164 | } |
165 | |
166 | data = Z_STRVAL(tmp); |
167 | switch (zend_hash_get_current_key_ex(target_hash, &string_key, &string_length, &num_key, 0, &pos)) { |
168 | case HASH_KEY_IS_STRING: |
169 | if (string_length == 0) { |
170 | goto next_element; |
171 | } |
172 | |
173 | l = string_length + el_len + 1; |
174 | memcpy(p, string_key, string_length); |
175 | strncat(p, "=" , 1); |
176 | strncat(p, data, el_len); |
177 | |
178 | #ifndef PHP_WIN32 |
179 | *ep = p; |
180 | ++ep; |
181 | #endif |
182 | p += l; |
183 | break; |
184 | case HASH_KEY_IS_LONG: |
185 | memcpy(p,data,el_len); |
186 | #ifndef PHP_WIN32 |
187 | *ep = p; |
188 | ++ep; |
189 | #endif |
190 | p += el_len + 1; |
191 | break; |
192 | case HASH_KEY_NON_EXISTENT: |
193 | break; |
194 | } |
195 | |
196 | next_element: |
197 | if (Z_TYPE_PP(element) != IS_STRING) { |
198 | zval_dtor(&tmp); |
199 | } |
200 | } |
201 | |
202 | assert((uint)(p - env.envp) <= sizeenv); |
203 | |
204 | zend_hash_internal_pointer_reset_ex(target_hash, &pos); |
205 | |
206 | return env; |
207 | } |
208 | /* }}} */ |
209 | |
210 | /* {{{ _php_free_envp */ |
211 | static void _php_free_envp(php_process_env_t env, int is_persistent) |
212 | { |
213 | #ifndef PHP_WIN32 |
214 | if (env.envarray) { |
215 | pefree(env.envarray, is_persistent); |
216 | } |
217 | #endif |
218 | if (env.envp) { |
219 | pefree(env.envp, is_persistent); |
220 | } |
221 | } |
222 | /* }}} */ |
223 | |
224 | /* {{{ proc_open_rsrc_dtor */ |
225 | static void proc_open_rsrc_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) |
226 | { |
227 | struct php_process_handle *proc = (struct php_process_handle*)rsrc->ptr; |
228 | int i; |
229 | #ifdef PHP_WIN32 |
230 | DWORD wstatus; |
231 | #elif HAVE_SYS_WAIT_H |
232 | int wstatus; |
233 | int waitpid_options = 0; |
234 | pid_t wait_pid; |
235 | #endif |
236 | |
237 | /* Close all handles to avoid a deadlock */ |
238 | for (i = 0; i < proc->npipes; i++) { |
239 | if (proc->pipes[i] != 0) { |
240 | zend_list_delete(proc->pipes[i]); |
241 | proc->pipes[i] = 0; |
242 | } |
243 | } |
244 | |
245 | #ifdef PHP_WIN32 |
246 | if (FG(pclose_wait)) { |
247 | WaitForSingleObject(proc->childHandle, INFINITE); |
248 | } |
249 | GetExitCodeProcess(proc->childHandle, &wstatus); |
250 | if (wstatus == STILL_ACTIVE) { |
251 | FG(pclose_ret) = -1; |
252 | } else { |
253 | FG(pclose_ret) = wstatus; |
254 | } |
255 | CloseHandle(proc->childHandle); |
256 | |
257 | #elif HAVE_SYS_WAIT_H |
258 | |
259 | if (!FG(pclose_wait)) { |
260 | waitpid_options = WNOHANG; |
261 | } |
262 | do { |
263 | wait_pid = waitpid(proc->child, &wstatus, waitpid_options); |
264 | } while (wait_pid == -1 && errno == EINTR); |
265 | |
266 | if (wait_pid <= 0) { |
267 | FG(pclose_ret) = -1; |
268 | } else { |
269 | if (WIFEXITED(wstatus)) |
270 | wstatus = WEXITSTATUS(wstatus); |
271 | FG(pclose_ret) = wstatus; |
272 | } |
273 | |
274 | #else |
275 | FG(pclose_ret) = -1; |
276 | #endif |
277 | _php_free_envp(proc->env, proc->is_persistent); |
278 | pefree(proc->command, proc->is_persistent); |
279 | pefree(proc, proc->is_persistent); |
280 | |
281 | } |
282 | /* }}} */ |
283 | |
284 | /* {{{ PHP_MINIT_FUNCTION(proc_open) */ |
285 | PHP_MINIT_FUNCTION(proc_open) |
286 | { |
287 | le_proc_open = zend_register_list_destructors_ex(proc_open_rsrc_dtor, NULL, "process" , module_number); |
288 | return SUCCESS; |
289 | } |
290 | /* }}} */ |
291 | |
292 | /* {{{ proto bool proc_terminate(resource process [, long signal]) |
293 | kill a process opened by proc_open */ |
294 | PHP_FUNCTION(proc_terminate) |
295 | { |
296 | zval *zproc; |
297 | struct php_process_handle *proc; |
298 | long sig_no = SIGTERM; |
299 | |
300 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r|l" , &zproc, &sig_no) == FAILURE) { |
301 | RETURN_FALSE; |
302 | } |
303 | |
304 | ZEND_FETCH_RESOURCE(proc, struct php_process_handle *, &zproc, -1, "process" , le_proc_open); |
305 | |
306 | #ifdef PHP_WIN32 |
307 | if (TerminateProcess(proc->childHandle, 255)) { |
308 | RETURN_TRUE; |
309 | } else { |
310 | RETURN_FALSE; |
311 | } |
312 | #else |
313 | if (kill(proc->child, sig_no) == 0) { |
314 | RETURN_TRUE; |
315 | } else { |
316 | RETURN_FALSE; |
317 | } |
318 | #endif |
319 | } |
320 | /* }}} */ |
321 | |
322 | /* {{{ proto int proc_close(resource process) |
323 | close a process opened by proc_open */ |
324 | PHP_FUNCTION(proc_close) |
325 | { |
326 | zval *zproc; |
327 | struct php_process_handle *proc; |
328 | |
329 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r" , &zproc) == FAILURE) { |
330 | RETURN_FALSE; |
331 | } |
332 | |
333 | ZEND_FETCH_RESOURCE(proc, struct php_process_handle *, &zproc, -1, "process" , le_proc_open); |
334 | |
335 | FG(pclose_wait) = 1; |
336 | zend_list_delete(Z_LVAL_P(zproc)); |
337 | FG(pclose_wait) = 0; |
338 | RETURN_LONG(FG(pclose_ret)); |
339 | } |
340 | /* }}} */ |
341 | |
342 | /* {{{ proto array proc_get_status(resource process) |
343 | get information about a process opened by proc_open */ |
344 | PHP_FUNCTION(proc_get_status) |
345 | { |
346 | zval *zproc; |
347 | struct php_process_handle *proc; |
348 | #ifdef PHP_WIN32 |
349 | DWORD wstatus; |
350 | #elif HAVE_SYS_WAIT_H |
351 | int wstatus; |
352 | pid_t wait_pid; |
353 | #endif |
354 | int running = 1, signaled = 0, stopped = 0; |
355 | int exitcode = -1, termsig = 0, stopsig = 0; |
356 | |
357 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r" , &zproc) == FAILURE) { |
358 | RETURN_FALSE; |
359 | } |
360 | |
361 | ZEND_FETCH_RESOURCE(proc, struct php_process_handle *, &zproc, -1, "process" , le_proc_open); |
362 | |
363 | array_init(return_value); |
364 | |
365 | add_assoc_string(return_value, "command" , proc->command, 1); |
366 | add_assoc_long(return_value, "pid" , (long) proc->child); |
367 | |
368 | #ifdef PHP_WIN32 |
369 | |
370 | GetExitCodeProcess(proc->childHandle, &wstatus); |
371 | |
372 | running = wstatus == STILL_ACTIVE; |
373 | exitcode = running ? -1 : wstatus; |
374 | |
375 | #elif HAVE_SYS_WAIT_H |
376 | |
377 | errno = 0; |
378 | wait_pid = waitpid(proc->child, &wstatus, WNOHANG|WUNTRACED); |
379 | |
380 | if (wait_pid == proc->child) { |
381 | if (WIFEXITED(wstatus)) { |
382 | running = 0; |
383 | exitcode = WEXITSTATUS(wstatus); |
384 | } |
385 | if (WIFSIGNALED(wstatus)) { |
386 | running = 0; |
387 | signaled = 1; |
388 | #ifdef NETWARE |
389 | termsig = WIFTERMSIG(wstatus); |
390 | #else |
391 | termsig = WTERMSIG(wstatus); |
392 | #endif |
393 | } |
394 | if (WIFSTOPPED(wstatus)) { |
395 | stopped = 1; |
396 | stopsig = WSTOPSIG(wstatus); |
397 | } |
398 | } else if (wait_pid == -1) { |
399 | running = 0; |
400 | } |
401 | #endif |
402 | |
403 | add_assoc_bool(return_value, "running" , running); |
404 | add_assoc_bool(return_value, "signaled" , signaled); |
405 | add_assoc_bool(return_value, "stopped" , stopped); |
406 | add_assoc_long(return_value, "exitcode" , exitcode); |
407 | add_assoc_long(return_value, "termsig" , termsig); |
408 | add_assoc_long(return_value, "stopsig" , stopsig); |
409 | } |
410 | /* }}} */ |
411 | |
412 | /* {{{ handy definitions for portability/readability */ |
413 | #ifdef PHP_WIN32 |
414 | # define pipe(pair) (CreatePipe(&pair[0], &pair[1], &security, 0) ? 0 : -1) |
415 | |
416 | # define COMSPEC_NT "cmd.exe" |
417 | |
418 | static inline HANDLE dup_handle(HANDLE src, BOOL inherit, BOOL closeorig) |
419 | { |
420 | HANDLE copy, self = GetCurrentProcess(); |
421 | |
422 | if (!DuplicateHandle(self, src, self, ©, 0, inherit, DUPLICATE_SAME_ACCESS | |
423 | (closeorig ? DUPLICATE_CLOSE_SOURCE : 0))) |
424 | return NULL; |
425 | return copy; |
426 | } |
427 | |
428 | static inline HANDLE dup_fd_as_handle(int fd) |
429 | { |
430 | return dup_handle((HANDLE)_get_osfhandle(fd), TRUE, FALSE); |
431 | } |
432 | |
433 | # define close_descriptor(fd) CloseHandle(fd) |
434 | #else |
435 | # define close_descriptor(fd) close(fd) |
436 | #endif |
437 | |
438 | #define DESC_PIPE 1 |
439 | #define DESC_FILE 2 |
440 | #define DESC_PARENT_MODE_WRITE 8 |
441 | |
442 | struct php_proc_open_descriptor_item { |
443 | int index; /* desired fd number in child process */ |
444 | php_file_descriptor_t parentend, childend; /* fds for pipes in parent/child */ |
445 | int mode; /* mode for proc_open code */ |
446 | int mode_flags; /* mode flags for opening fds */ |
447 | }; |
448 | /* }}} */ |
449 | |
450 | /* {{{ proto resource proc_open(string command, array descriptorspec, array &pipes [, string cwd [, array env [, array other_options]]]) |
451 | Run a process with more control over it's file descriptors */ |
452 | PHP_FUNCTION(proc_open) |
453 | { |
454 | char *command, *cwd=NULL; |
455 | int command_len, cwd_len = 0; |
456 | zval *descriptorspec; |
457 | zval *pipes; |
458 | zval *environment = NULL; |
459 | zval *other_options = NULL; |
460 | php_process_env_t env; |
461 | int ndesc = 0; |
462 | int i; |
463 | zval **descitem = NULL; |
464 | HashPosition pos; |
465 | struct php_proc_open_descriptor_item descriptors[PHP_PROC_OPEN_MAX_DESCRIPTORS]; |
466 | #ifdef PHP_WIN32 |
467 | PROCESS_INFORMATION pi; |
468 | HANDLE childHandle; |
469 | STARTUPINFO si; |
470 | BOOL newprocok; |
471 | SECURITY_ATTRIBUTES security; |
472 | DWORD dwCreateFlags = 0; |
473 | char *command_with_cmd; |
474 | UINT old_error_mode; |
475 | char cur_cwd[MAXPATHLEN]; |
476 | #endif |
477 | #ifdef NETWARE |
478 | char** child_argv = NULL; |
479 | char* command_dup = NULL; |
480 | char* orig_cwd = NULL; |
481 | int command_num_args = 0; |
482 | wiring_t channel; |
483 | #endif |
484 | php_process_id_t child; |
485 | struct php_process_handle *proc; |
486 | int is_persistent = 0; /* TODO: ensure that persistent procs will work */ |
487 | #ifdef PHP_WIN32 |
488 | int suppress_errors = 0; |
489 | int bypass_shell = 0; |
490 | #endif |
491 | #if PHP_CAN_DO_PTS |
492 | php_file_descriptor_t dev_ptmx = -1; /* master */ |
493 | php_file_descriptor_t slave_pty = -1; |
494 | #endif |
495 | |
496 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "saz|s!a!a!" , &command, |
497 | &command_len, &descriptorspec, &pipes, &cwd, &cwd_len, &environment, |
498 | &other_options) == FAILURE) { |
499 | RETURN_FALSE; |
500 | } |
501 | |
502 | command = pestrdup(command, is_persistent); |
503 | |
504 | #ifdef PHP_WIN32 |
505 | if (other_options) { |
506 | zval **item; |
507 | if (SUCCESS == zend_hash_find(Z_ARRVAL_P(other_options), "suppress_errors" , sizeof("suppress_errors" ), (void**)&item)) { |
508 | if ((Z_TYPE_PP(item) == IS_BOOL || Z_TYPE_PP(item) == IS_LONG) && |
509 | Z_LVAL_PP(item)) { |
510 | suppress_errors = 1; |
511 | } |
512 | } |
513 | if (SUCCESS == zend_hash_find(Z_ARRVAL_P(other_options), "bypass_shell" , sizeof("bypass_shell" ), (void**)&item)) { |
514 | if ((Z_TYPE_PP(item) == IS_BOOL || Z_TYPE_PP(item) == IS_LONG) && |
515 | Z_LVAL_PP(item)) { |
516 | bypass_shell = 1; |
517 | } |
518 | } |
519 | } |
520 | #endif |
521 | |
522 | command_len = strlen(command); |
523 | |
524 | if (environment) { |
525 | env = _php_array_to_envp(environment, is_persistent TSRMLS_CC); |
526 | } else { |
527 | memset(&env, 0, sizeof(env)); |
528 | } |
529 | |
530 | memset(descriptors, 0, sizeof(descriptors)); |
531 | |
532 | #ifdef PHP_WIN32 |
533 | /* we use this to allow the child to inherit handles */ |
534 | memset(&security, 0, sizeof(security)); |
535 | security.nLength = sizeof(security); |
536 | security.bInheritHandle = TRUE; |
537 | security.lpSecurityDescriptor = NULL; |
538 | #endif |
539 | |
540 | /* walk the descriptor spec and set up files/pipes */ |
541 | zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(descriptorspec), &pos); |
542 | while (zend_hash_get_current_data_ex(Z_ARRVAL_P(descriptorspec), (void **)&descitem, &pos) == SUCCESS) { |
543 | char *str_index; |
544 | ulong nindex; |
545 | zval **ztype; |
546 | |
547 | str_index = NULL; |
548 | zend_hash_get_current_key_ex(Z_ARRVAL_P(descriptorspec), &str_index, NULL, &nindex, 0, &pos); |
549 | |
550 | if (str_index) { |
551 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "descriptor spec must be an integer indexed array" ); |
552 | goto exit_fail; |
553 | } |
554 | |
555 | descriptors[ndesc].index = nindex; |
556 | |
557 | if (Z_TYPE_PP(descitem) == IS_RESOURCE) { |
558 | /* should be a stream - try and dup the descriptor */ |
559 | php_stream *stream; |
560 | php_socket_t fd; |
561 | |
562 | php_stream_from_zval(stream, descitem); |
563 | |
564 | if (FAILURE == php_stream_cast(stream, PHP_STREAM_AS_FD, (void **)&fd, REPORT_ERRORS)) { |
565 | goto exit_fail; |
566 | } |
567 | |
568 | #ifdef PHP_WIN32 |
569 | descriptors[ndesc].childend = dup_fd_as_handle(fd); |
570 | if (descriptors[ndesc].childend == NULL) { |
571 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to dup File-Handle for descriptor %d" , nindex); |
572 | goto exit_fail; |
573 | } |
574 | #else |
575 | descriptors[ndesc].childend = dup(fd); |
576 | if (descriptors[ndesc].childend < 0) { |
577 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to dup File-Handle for descriptor %ld - %s" , nindex, strerror(errno)); |
578 | goto exit_fail; |
579 | } |
580 | #endif |
581 | descriptors[ndesc].mode = DESC_FILE; |
582 | |
583 | } else if (Z_TYPE_PP(descitem) != IS_ARRAY) { |
584 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "Descriptor item must be either an array or a File-Handle" ); |
585 | goto exit_fail; |
586 | } else { |
587 | |
588 | if (zend_hash_index_find(Z_ARRVAL_PP(descitem), 0, (void **)&ztype) == SUCCESS) { |
589 | convert_to_string_ex(ztype); |
590 | } else { |
591 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "Missing handle qualifier in array" ); |
592 | goto exit_fail; |
593 | } |
594 | |
595 | if (strcmp(Z_STRVAL_PP(ztype), "pipe" ) == 0) { |
596 | php_file_descriptor_t newpipe[2]; |
597 | zval **zmode; |
598 | |
599 | if (zend_hash_index_find(Z_ARRVAL_PP(descitem), 1, (void **)&zmode) == SUCCESS) { |
600 | convert_to_string_ex(zmode); |
601 | } else { |
602 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "Missing mode parameter for 'pipe'" ); |
603 | goto exit_fail; |
604 | } |
605 | |
606 | descriptors[ndesc].mode = DESC_PIPE; |
607 | |
608 | if (0 != pipe(newpipe)) { |
609 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to create pipe %s" , strerror(errno)); |
610 | goto exit_fail; |
611 | } |
612 | |
613 | if (strncmp(Z_STRVAL_PP(zmode), "w" , 1) != 0) { |
614 | descriptors[ndesc].parentend = newpipe[1]; |
615 | descriptors[ndesc].childend = newpipe[0]; |
616 | descriptors[ndesc].mode |= DESC_PARENT_MODE_WRITE; |
617 | } else { |
618 | descriptors[ndesc].parentend = newpipe[0]; |
619 | descriptors[ndesc].childend = newpipe[1]; |
620 | } |
621 | #ifdef PHP_WIN32 |
622 | /* don't let the child inherit the parent side of the pipe */ |
623 | descriptors[ndesc].parentend = dup_handle(descriptors[ndesc].parentend, FALSE, TRUE); |
624 | #endif |
625 | descriptors[ndesc].mode_flags = descriptors[ndesc].mode & DESC_PARENT_MODE_WRITE ? O_WRONLY : O_RDONLY; |
626 | #ifdef PHP_WIN32 |
627 | if (Z_STRLEN_PP(zmode) >= 2 && Z_STRVAL_PP(zmode)[1] == 'b') |
628 | descriptors[ndesc].mode_flags |= O_BINARY; |
629 | #endif |
630 | |
631 | } else if (strcmp(Z_STRVAL_PP(ztype), "file" ) == 0) { |
632 | zval **zfile, **zmode; |
633 | php_socket_t fd; |
634 | php_stream *stream; |
635 | |
636 | descriptors[ndesc].mode = DESC_FILE; |
637 | |
638 | if (zend_hash_index_find(Z_ARRVAL_PP(descitem), 1, (void **)&zfile) == SUCCESS) { |
639 | convert_to_string_ex(zfile); |
640 | } else { |
641 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "Missing file name parameter for 'file'" ); |
642 | goto exit_fail; |
643 | } |
644 | |
645 | if (zend_hash_index_find(Z_ARRVAL_PP(descitem), 2, (void **)&zmode) == SUCCESS) { |
646 | convert_to_string_ex(zmode); |
647 | } else { |
648 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "Missing mode parameter for 'file'" ); |
649 | goto exit_fail; |
650 | } |
651 | |
652 | /* try a wrapper */ |
653 | stream = php_stream_open_wrapper(Z_STRVAL_PP(zfile), Z_STRVAL_PP(zmode), |
654 | REPORT_ERRORS|STREAM_WILL_CAST, NULL); |
655 | |
656 | /* force into an fd */ |
657 | if (stream == NULL || FAILURE == php_stream_cast(stream, |
658 | PHP_STREAM_CAST_RELEASE|PHP_STREAM_AS_FD, |
659 | (void **)&fd, REPORT_ERRORS)) { |
660 | goto exit_fail; |
661 | } |
662 | |
663 | #ifdef PHP_WIN32 |
664 | descriptors[ndesc].childend = dup_fd_as_handle(fd); |
665 | _close(fd); |
666 | |
667 | /* simulate the append mode by fseeking to the end of the file |
668 | this introduces a potential race-condition, but it is the best we can do, though */ |
669 | if (strchr(Z_STRVAL_PP(zmode), 'a')) { |
670 | SetFilePointer(descriptors[ndesc].childend, 0, NULL, FILE_END); |
671 | } |
672 | #else |
673 | descriptors[ndesc].childend = fd; |
674 | #endif |
675 | } else if (strcmp(Z_STRVAL_PP(ztype), "pty" ) == 0) { |
676 | #if PHP_CAN_DO_PTS |
677 | if (dev_ptmx == -1) { |
678 | /* open things up */ |
679 | dev_ptmx = open("/dev/ptmx" , O_RDWR); |
680 | if (dev_ptmx == -1) { |
681 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to open /dev/ptmx, errno %d" , errno); |
682 | goto exit_fail; |
683 | } |
684 | grantpt(dev_ptmx); |
685 | unlockpt(dev_ptmx); |
686 | slave_pty = open(ptsname(dev_ptmx), O_RDWR); |
687 | |
688 | if (slave_pty == -1) { |
689 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to open slave pty, errno %d" , errno); |
690 | goto exit_fail; |
691 | } |
692 | } |
693 | descriptors[ndesc].mode = DESC_PIPE; |
694 | descriptors[ndesc].childend = dup(slave_pty); |
695 | descriptors[ndesc].parentend = dup(dev_ptmx); |
696 | descriptors[ndesc].mode_flags = O_RDWR; |
697 | #else |
698 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "pty pseudo terminal not supported on this system" ); |
699 | goto exit_fail; |
700 | #endif |
701 | } else { |
702 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s is not a valid descriptor spec/mode" , Z_STRVAL_PP(ztype)); |
703 | goto exit_fail; |
704 | } |
705 | } |
706 | |
707 | zend_hash_move_forward_ex(Z_ARRVAL_P(descriptorspec), &pos); |
708 | if (++ndesc == PHP_PROC_OPEN_MAX_DESCRIPTORS) |
709 | break; |
710 | } |
711 | |
712 | #ifdef PHP_WIN32 |
713 | if (cwd == NULL) { |
714 | char *getcwd_result; |
715 | getcwd_result = VCWD_GETCWD(cur_cwd, MAXPATHLEN); |
716 | if (!getcwd_result) { |
717 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot get current directory" ); |
718 | goto exit_fail; |
719 | } |
720 | cwd = cur_cwd; |
721 | } |
722 | |
723 | memset(&si, 0, sizeof(si)); |
724 | si.cb = sizeof(si); |
725 | si.dwFlags = STARTF_USESTDHANDLES; |
726 | |
727 | si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); |
728 | si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); |
729 | si.hStdError = GetStdHandle(STD_ERROR_HANDLE); |
730 | |
731 | /* redirect stdin/stdout/stderr if requested */ |
732 | for (i = 0; i < ndesc; i++) { |
733 | switch(descriptors[i].index) { |
734 | case 0: |
735 | si.hStdInput = descriptors[i].childend; |
736 | break; |
737 | case 1: |
738 | si.hStdOutput = descriptors[i].childend; |
739 | break; |
740 | case 2: |
741 | si.hStdError = descriptors[i].childend; |
742 | break; |
743 | } |
744 | } |
745 | |
746 | |
747 | memset(&pi, 0, sizeof(pi)); |
748 | |
749 | if (suppress_errors) { |
750 | old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX); |
751 | } |
752 | |
753 | dwCreateFlags = NORMAL_PRIORITY_CLASS; |
754 | if(strcmp(sapi_module.name, "cli" ) != 0) { |
755 | dwCreateFlags |= CREATE_NO_WINDOW; |
756 | } |
757 | |
758 | if (bypass_shell) { |
759 | newprocok = CreateProcess(NULL, command, &security, &security, TRUE, dwCreateFlags, env.envp, cwd, &si, &pi); |
760 | } else { |
761 | spprintf(&command_with_cmd, 0, "%s /c %s" , COMSPEC_NT, command); |
762 | |
763 | newprocok = CreateProcess(NULL, command_with_cmd, &security, &security, TRUE, dwCreateFlags, env.envp, cwd, &si, &pi); |
764 | |
765 | efree(command_with_cmd); |
766 | } |
767 | |
768 | if (suppress_errors) { |
769 | SetErrorMode(old_error_mode); |
770 | } |
771 | |
772 | if (FALSE == newprocok) { |
773 | DWORD dw = GetLastError(); |
774 | |
775 | /* clean up all the descriptors */ |
776 | for (i = 0; i < ndesc; i++) { |
777 | CloseHandle(descriptors[i].childend); |
778 | if (descriptors[i].parentend) { |
779 | CloseHandle(descriptors[i].parentend); |
780 | } |
781 | } |
782 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "CreateProcess failed, error code - %u" , dw); |
783 | goto exit_fail; |
784 | } |
785 | |
786 | childHandle = pi.hProcess; |
787 | child = pi.dwProcessId; |
788 | CloseHandle(pi.hThread); |
789 | |
790 | #elif defined(NETWARE) |
791 | if (cwd) { |
792 | orig_cwd = getcwd(NULL, PATH_MAX); |
793 | chdir2(cwd); |
794 | } |
795 | channel.infd = descriptors[0].childend; |
796 | channel.outfd = descriptors[1].childend; |
797 | channel.errfd = -1; |
798 | /* Duplicate the command as processing downwards will modify it*/ |
799 | command_dup = strdup(command); |
800 | if (!command_dup) { |
801 | goto exit_fail; |
802 | } |
803 | /* get a number of args */ |
804 | construct_argc_argv(command_dup, NULL, &command_num_args, NULL); |
805 | child_argv = (char**) malloc((command_num_args + 1) * sizeof(char*)); |
806 | if(!child_argv) { |
807 | free(command_dup); |
808 | if (cwd && orig_cwd) { |
809 | chdir2(orig_cwd); |
810 | free(orig_cwd); |
811 | } |
812 | } |
813 | /* fill the child arg vector */ |
814 | construct_argc_argv(command_dup, NULL, &command_num_args, child_argv); |
815 | child_argv[command_num_args] = NULL; |
816 | child = procve(child_argv[0], PROC_DETACHED|PROC_INHERIT_CWD, NULL, &channel, NULL, NULL, 0, NULL, (const char**)child_argv); |
817 | free(child_argv); |
818 | free(command_dup); |
819 | if (cwd && orig_cwd) { |
820 | chdir2(orig_cwd); |
821 | free(orig_cwd); |
822 | } |
823 | if (child < 0) { |
824 | /* failed to fork() */ |
825 | /* clean up all the descriptors */ |
826 | for (i = 0; i < ndesc; i++) { |
827 | close(descriptors[i].childend); |
828 | if (descriptors[i].parentend) |
829 | close(descriptors[i].parentend); |
830 | } |
831 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "procve failed - %s" , strerror(errno)); |
832 | goto exit_fail; |
833 | } |
834 | #elif HAVE_FORK |
835 | /* the unix way */ |
836 | child = fork(); |
837 | |
838 | if (child == 0) { |
839 | /* this is the child process */ |
840 | |
841 | #if PHP_CAN_DO_PTS |
842 | if (dev_ptmx >= 0) { |
843 | int my_pid = getpid(); |
844 | |
845 | #ifdef TIOCNOTTY |
846 | /* detach from original tty. Might only need this if isatty(0) is true */ |
847 | ioctl(0,TIOCNOTTY,NULL); |
848 | #else |
849 | setsid(); |
850 | #endif |
851 | /* become process group leader */ |
852 | setpgid(my_pid, my_pid); |
853 | tcsetpgrp(0, my_pid); |
854 | } |
855 | #endif |
856 | |
857 | /* close those descriptors that we just opened for the parent stuff, |
858 | * dup new descriptors into required descriptors and close the original |
859 | * cruft */ |
860 | for (i = 0; i < ndesc; i++) { |
861 | switch (descriptors[i].mode & ~DESC_PARENT_MODE_WRITE) { |
862 | case DESC_PIPE: |
863 | close(descriptors[i].parentend); |
864 | break; |
865 | } |
866 | if (dup2(descriptors[i].childend, descriptors[i].index) < 0) |
867 | perror("dup2" ); |
868 | if (descriptors[i].childend != descriptors[i].index) |
869 | close(descriptors[i].childend); |
870 | } |
871 | |
872 | #if PHP_CAN_DO_PTS |
873 | if (dev_ptmx >= 0) { |
874 | close(dev_ptmx); |
875 | close(slave_pty); |
876 | } |
877 | #endif |
878 | |
879 | if (cwd) { |
880 | php_ignore_value(chdir(cwd)); |
881 | } |
882 | |
883 | if (env.envarray) { |
884 | execle("/bin/sh" , "sh" , "-c" , command, NULL, env.envarray); |
885 | } else { |
886 | execl("/bin/sh" , "sh" , "-c" , command, NULL); |
887 | } |
888 | _exit(127); |
889 | |
890 | } else if (child < 0) { |
891 | /* failed to fork() */ |
892 | |
893 | /* clean up all the descriptors */ |
894 | for (i = 0; i < ndesc; i++) { |
895 | close(descriptors[i].childend); |
896 | if (descriptors[i].parentend) |
897 | close(descriptors[i].parentend); |
898 | } |
899 | |
900 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "fork failed - %s" , strerror(errno)); |
901 | |
902 | goto exit_fail; |
903 | |
904 | } |
905 | #else |
906 | # error You lose (configure should not have let you get here) |
907 | #endif |
908 | /* we forked/spawned and this is the parent */ |
909 | |
910 | proc = (struct php_process_handle*)pemalloc(sizeof(struct php_process_handle), is_persistent); |
911 | proc->is_persistent = is_persistent; |
912 | proc->command = command; |
913 | proc->npipes = ndesc; |
914 | proc->child = child; |
915 | #ifdef PHP_WIN32 |
916 | proc->childHandle = childHandle; |
917 | #endif |
918 | proc->env = env; |
919 | |
920 | if (pipes != NULL) { |
921 | zval_dtor(pipes); |
922 | } |
923 | array_init(pipes); |
924 | |
925 | #if PHP_CAN_DO_PTS |
926 | if (dev_ptmx >= 0) { |
927 | close(dev_ptmx); |
928 | close(slave_pty); |
929 | } |
930 | #endif |
931 | |
932 | /* clean up all the child ends and then open streams on the parent |
933 | * ends, where appropriate */ |
934 | for (i = 0; i < ndesc; i++) { |
935 | char *mode_string=NULL; |
936 | php_stream *stream = NULL; |
937 | |
938 | close_descriptor(descriptors[i].childend); |
939 | |
940 | switch (descriptors[i].mode & ~DESC_PARENT_MODE_WRITE) { |
941 | case DESC_PIPE: |
942 | switch(descriptors[i].mode_flags) { |
943 | #ifdef PHP_WIN32 |
944 | case O_WRONLY|O_BINARY: |
945 | mode_string = "wb" ; |
946 | break; |
947 | case O_RDONLY|O_BINARY: |
948 | mode_string = "rb" ; |
949 | break; |
950 | #endif |
951 | case O_WRONLY: |
952 | mode_string = "w" ; |
953 | break; |
954 | case O_RDONLY: |
955 | mode_string = "r" ; |
956 | break; |
957 | case O_RDWR: |
958 | mode_string = "r+" ; |
959 | break; |
960 | } |
961 | #ifdef PHP_WIN32 |
962 | stream = php_stream_fopen_from_fd(_open_osfhandle((zend_intptr_t)descriptors[i].parentend, |
963 | descriptors[i].mode_flags), mode_string, NULL); |
964 | #else |
965 | stream = php_stream_fopen_from_fd(descriptors[i].parentend, mode_string, NULL); |
966 | # if defined(F_SETFD) && defined(FD_CLOEXEC) |
967 | /* mark the descriptor close-on-exec, so that it won't be inherited by potential other children */ |
968 | fcntl(descriptors[i].parentend, F_SETFD, FD_CLOEXEC); |
969 | # endif |
970 | #endif |
971 | if (stream) { |
972 | zval *retfp; |
973 | |
974 | /* nasty hack; don't copy it */ |
975 | stream->flags |= PHP_STREAM_FLAG_NO_SEEK; |
976 | |
977 | MAKE_STD_ZVAL(retfp); |
978 | php_stream_to_zval(stream, retfp); |
979 | add_index_zval(pipes, descriptors[i].index, retfp); |
980 | |
981 | proc->pipes[i] = Z_LVAL_P(retfp); |
982 | } |
983 | break; |
984 | default: |
985 | proc->pipes[i] = 0; |
986 | } |
987 | } |
988 | |
989 | ZEND_REGISTER_RESOURCE(return_value, proc, le_proc_open); |
990 | return; |
991 | |
992 | exit_fail: |
993 | _php_free_envp(env, is_persistent); |
994 | pefree(command, is_persistent); |
995 | #if PHP_CAN_DO_PTS |
996 | if (dev_ptmx >= 0) { |
997 | close(dev_ptmx); |
998 | } |
999 | if (slave_pty >= 0) { |
1000 | close(slave_pty); |
1001 | } |
1002 | #endif |
1003 | RETURN_FALSE; |
1004 | |
1005 | } |
1006 | /* }}} */ |
1007 | |
1008 | #endif /* PHP_CAN_SUPPORT_PROC_OPEN */ |
1009 | |
1010 | /* |
1011 | * Local variables: |
1012 | * tab-width: 4 |
1013 | * c-basic-offset: 4 |
1014 | * End: |
1015 | * vim600: sw=4 ts=4 fdm=marker |
1016 | * vim<600: sw=4 ts=4 |
1017 | */ |
1018 | |