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 | http://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: Marcus Boerger <helly@php.net> |
14 | Johannes Schlueter <johannes@php.net> |
15 +----------------------------------------------------------------------+
16 */
17
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21
22 #include "php.h"
23
24 #ifndef HAVE_RL_COMPLETION_MATCHES
25 #define rl_completion_matches completion_matches
26 #endif
27
28 #include "php_globals.h"
29 #include "php_variables.h"
30 #include "zend_hash.h"
31 #include "zend_modules.h"
32
33 #include "SAPI.h"
34 #include <locale.h>
35 #include "zend.h"
36 #include "zend_extensions.h"
37 #include "php_ini.h"
38 #include "php_globals.h"
39 #include "php_main.h"
40 #include "fopen_wrappers.h"
41 #include "ext/standard/php_standard.h"
42 #include "zend_smart_str.h"
43
44 #ifdef __riscos__
45 #include <unixlib/local.h>
46 #endif
47
48 #if HAVE_LIBEDIT
49 #include <editline/readline.h>
50 #else
51 #include <readline/readline.h>
52 #include <readline/history.h>
53 #endif
54
55 #include "zend_compile.h"
56 #include "zend_execute.h"
57 #include "zend_highlight.h"
58 #include "zend_exceptions.h"
59
60 #include "sapi/cli/cli.h"
61 #include "readline_cli.h"
62
63 #if defined(COMPILE_DL_READLINE) && !defined(PHP_WIN32)
64 #include <dlfcn.h>
65 #endif
66
67 #ifndef RTLD_DEFAULT
68 #define RTLD_DEFAULT NULL
69 #endif
70
71 #define DEFAULT_PROMPT "\\b \\> "
72
73 ZEND_DECLARE_MODULE_GLOBALS(cli_readline)
74
75 static char php_last_char = '\0';
76 static FILE *pager_pipe = NULL;
77
readline_shell_write(const char * str,size_t str_length)78 static size_t readline_shell_write(const char *str, size_t str_length) /* {{{ */
79 {
80 if (CLIR_G(prompt_str)) {
81 smart_str_appendl(CLIR_G(prompt_str), str, str_length);
82 return str_length;
83 }
84
85 if (CLIR_G(pager) && *CLIR_G(pager) && !pager_pipe) {
86 pager_pipe = VCWD_POPEN(CLIR_G(pager), "w");
87 }
88 if (pager_pipe) {
89 return fwrite(str, 1, MIN(str_length, 16384), pager_pipe);
90 }
91
92 return -1;
93 }
94 /* }}} */
95
readline_shell_ub_write(const char * str,size_t str_length)96 static size_t readline_shell_ub_write(const char *str, size_t str_length) /* {{{ */
97 {
98 /* We just store the last char here and then pass back to the
99 caller (sapi_cli_single_write in sapi/cli) which will actually
100 write due to -1 return code */
101 php_last_char = str[str_length-1];
102
103 return (size_t) -1;
104 }
105 /* }}} */
106
cli_readline_init_globals(zend_cli_readline_globals * rg)107 static void cli_readline_init_globals(zend_cli_readline_globals *rg)
108 {
109 rg->pager = NULL;
110 rg->prompt = NULL;
111 rg->prompt_str = NULL;
112 }
113
114 PHP_INI_BEGIN()
115 STD_PHP_INI_ENTRY("cli.pager", "", PHP_INI_ALL, OnUpdateString, pager, zend_cli_readline_globals, cli_readline_globals)
116 STD_PHP_INI_ENTRY("cli.prompt", DEFAULT_PROMPT, PHP_INI_ALL, OnUpdateString, prompt, zend_cli_readline_globals, cli_readline_globals)
117 PHP_INI_END()
118
119
120
121 typedef enum {
122 body,
123 sstring,
124 dstring,
125 sstring_esc,
126 dstring_esc,
127 comment_line,
128 comment_block,
129 heredoc_start,
130 heredoc,
131 outside,
132 } php_code_type;
133
cli_get_prompt(char * block,char prompt)134 static zend_string *cli_get_prompt(char *block, char prompt) /* {{{ */
135 {
136 smart_str retval = {0};
137 char *prompt_spec = CLIR_G(prompt) ? CLIR_G(prompt) : DEFAULT_PROMPT;
138
139 do {
140 if (*prompt_spec == '\\') {
141 switch (prompt_spec[1]) {
142 case '\\':
143 smart_str_appendc(&retval, '\\');
144 prompt_spec++;
145 break;
146 case 'n':
147 smart_str_appendc(&retval, '\n');
148 prompt_spec++;
149 break;
150 case 't':
151 smart_str_appendc(&retval, '\t');
152 prompt_spec++;
153 break;
154 case 'e':
155 smart_str_appendc(&retval, '\033');
156 prompt_spec++;
157 break;
158
159
160 case 'v':
161 smart_str_appends(&retval, PHP_VERSION);
162 prompt_spec++;
163 break;
164 case 'b':
165 smart_str_appends(&retval, block);
166 prompt_spec++;
167 break;
168 case '>':
169 smart_str_appendc(&retval, prompt);
170 prompt_spec++;
171 break;
172 case '`':
173 smart_str_appendc(&retval, '`');
174 prompt_spec++;
175 break;
176 default:
177 smart_str_appendc(&retval, '\\');
178 break;
179 }
180 } else if (*prompt_spec == '`') {
181 char *prompt_end = strstr(prompt_spec + 1, "`");
182 char *code;
183
184 if (prompt_end) {
185 code = estrndup(prompt_spec + 1, prompt_end - prompt_spec - 1);
186
187 CLIR_G(prompt_str) = &retval;
188 zend_try {
189 zend_eval_stringl(code, prompt_end - prompt_spec - 1, NULL, "php prompt code");
190 } zend_end_try();
191 CLIR_G(prompt_str) = NULL;
192 efree(code);
193 prompt_spec = prompt_end;
194 }
195 } else {
196 smart_str_appendc(&retval, *prompt_spec);
197 }
198 } while (++prompt_spec && *prompt_spec);
199 smart_str_0(&retval);
200 return retval.s;
201 }
202 /* }}} */
203
cli_is_valid_code(char * code,size_t len,zend_string ** prompt)204 static int cli_is_valid_code(char *code, size_t len, zend_string **prompt) /* {{{ */
205 {
206 int valid_end = 1, last_valid_end;
207 int brackets_count = 0;
208 int brace_count = 0;
209 size_t i;
210 php_code_type code_type = body;
211 char *heredoc_tag = NULL;
212 size_t heredoc_len;
213
214 for (i = 0; i < len; ++i) {
215 switch(code_type) {
216 default:
217 switch(code[i]) {
218 case '{':
219 brackets_count++;
220 valid_end = 0;
221 break;
222 case '}':
223 if (brackets_count > 0) {
224 brackets_count--;
225 }
226 valid_end = brackets_count ? 0 : 1;
227 break;
228 case '(':
229 brace_count++;
230 valid_end = 0;
231 break;
232 case ')':
233 if (brace_count > 0) {
234 brace_count--;
235 }
236 valid_end = 0;
237 break;
238 case ';':
239 valid_end = brace_count == 0 && brackets_count == 0;
240 break;
241 case ' ':
242 case '\r':
243 case '\n':
244 case '\t':
245 break;
246 case '\'':
247 code_type = sstring;
248 break;
249 case '"':
250 code_type = dstring;
251 break;
252 case '#':
253 if (code[i+1] == '[') {
254 valid_end = 0;
255 break;
256 }
257 code_type = comment_line;
258 break;
259 case '/':
260 if (code[i+1] == '/') {
261 i++;
262 code_type = comment_line;
263 break;
264 }
265 if (code[i+1] == '*') {
266 last_valid_end = valid_end;
267 valid_end = 0;
268 code_type = comment_block;
269 i++;
270 break;
271 }
272 valid_end = 0;
273 break;
274 case '?':
275 if (code[i+1] == '>') {
276 i++;
277 code_type = outside;
278 break;
279 }
280 valid_end = 0;
281 break;
282 case '<':
283 valid_end = 0;
284 if (i + 2 < len && code[i+1] == '<' && code[i+2] == '<') {
285 i += 2;
286 code_type = heredoc_start;
287 heredoc_tag = NULL;
288 heredoc_len = 0;
289 }
290 break;
291 default:
292 valid_end = 0;
293 break;
294 }
295 break;
296 case sstring:
297 if (code[i] == '\\') {
298 code_type = sstring_esc;
299 } else {
300 if (code[i] == '\'') {
301 code_type = body;
302 }
303 }
304 break;
305 case sstring_esc:
306 code_type = sstring;
307 break;
308 case dstring:
309 if (code[i] == '\\') {
310 code_type = dstring_esc;
311 } else {
312 if (code[i] == '"') {
313 code_type = body;
314 }
315 }
316 break;
317 case dstring_esc:
318 code_type = dstring;
319 break;
320 case comment_line:
321 if (code[i] == '\n') {
322 code_type = body;
323 }
324 break;
325 case comment_block:
326 if (code[i-1] == '*' && code[i] == '/') {
327 code_type = body;
328 valid_end = last_valid_end;
329 }
330 break;
331 case heredoc_start:
332 switch(code[i]) {
333 case ' ':
334 case '\t':
335 case '\'':
336 break;
337 case '\r':
338 case '\n':
339 if (heredoc_tag) {
340 code_type = heredoc;
341 } else {
342 /* Malformed heredoc without label */
343 code_type = body;
344 }
345 break;
346 default:
347 if (!heredoc_tag) {
348 heredoc_tag = code+i;
349 }
350 heredoc_len++;
351 break;
352 }
353 break;
354 case heredoc:
355 ZEND_ASSERT(heredoc_tag);
356 if (!strncmp(code + i - heredoc_len + 1, heredoc_tag, heredoc_len)) {
357 unsigned char c = code[i + 1];
358 char *p = code + i - heredoc_len;
359
360 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c >= 0x80) break;
361 while (*p == ' ' || *p == '\t') p--;
362 if (*p != '\n') break;
363 code_type = body;
364 }
365 break;
366 case outside:
367 if ((CG(short_tags) && !strncmp(code+i-1, "<?", 2))
368 || (i > 3 && !strncmp(code+i-4, "<?php", 5))
369 ) {
370 code_type = body;
371 }
372 break;
373 }
374 }
375
376 switch (code_type) {
377 default:
378 if (brace_count) {
379 *prompt = cli_get_prompt("php", '(');
380 } else if (brackets_count) {
381 *prompt = cli_get_prompt("php", '{');
382 } else {
383 *prompt = cli_get_prompt("php", '>');
384 }
385 break;
386 case sstring:
387 case sstring_esc:
388 *prompt = cli_get_prompt("php", '\'');
389 break;
390 case dstring:
391 case dstring_esc:
392 *prompt = cli_get_prompt("php", '"');
393 break;
394 case comment_block:
395 *prompt = cli_get_prompt("/* ", '>');
396 break;
397 case heredoc:
398 *prompt = cli_get_prompt("<<<", '>');
399 break;
400 case outside:
401 *prompt = cli_get_prompt(" ", '>');
402 break;
403 }
404
405 if (!valid_end || brackets_count) {
406 return 0;
407 } else {
408 return 1;
409 }
410 }
411 /* }}} */
412
cli_completion_generator_ht(const char * text,size_t textlen,int * state,HashTable * ht,void ** pData)413 static char *cli_completion_generator_ht(const char *text, size_t textlen, int *state, HashTable *ht, void **pData) /* {{{ */
414 {
415 zend_string *name;
416 zend_ulong number;
417
418 if (!(*state % 2)) {
419 zend_hash_internal_pointer_reset(ht);
420 (*state)++;
421 }
422 while(zend_hash_has_more_elements(ht) == SUCCESS) {
423 zend_hash_get_current_key(ht, &name, &number);
424 if (!textlen || !strncmp(ZSTR_VAL(name), text, textlen)) {
425 if (pData) {
426 *pData = zend_hash_get_current_data_ptr(ht);
427 }
428 zend_hash_move_forward(ht);
429 return ZSTR_VAL(name);
430 }
431 if (zend_hash_move_forward(ht) == FAILURE) {
432 break;
433 }
434 }
435 (*state)++;
436 return NULL;
437 } /* }}} */
438
cli_completion_generator_var(const char * text,size_t textlen,int * state)439 static char *cli_completion_generator_var(const char *text, size_t textlen, int *state) /* {{{ */
440 {
441 char *retval, *tmp;
442 zend_array *symbol_table = &EG(symbol_table);
443
444 tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, symbol_table, NULL);
445 if (retval) {
446 retval = malloc(strlen(tmp) + 2);
447 retval[0] = '$';
448 strcpy(&retval[1], tmp);
449 rl_completion_append_character = '\0';
450 }
451 return retval;
452 } /* }}} */
453
cli_completion_generator_ini(const char * text,size_t textlen,int * state)454 static char *cli_completion_generator_ini(const char *text, size_t textlen, int *state) /* {{{ */
455 {
456 char *retval, *tmp;
457
458 tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, EG(ini_directives), NULL);
459 if (retval) {
460 retval = malloc(strlen(tmp) + 2);
461 retval[0] = '#';
462 strcpy(&retval[1], tmp);
463 rl_completion_append_character = '=';
464 }
465 return retval;
466 } /* }}} */
467
cli_completion_generator_func(const char * text,size_t textlen,int * state,HashTable * ht)468 static char *cli_completion_generator_func(const char *text, size_t textlen, int *state, HashTable *ht) /* {{{ */
469 {
470 zend_function *func;
471 char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&func);
472 if (retval) {
473 rl_completion_append_character = '(';
474 retval = strdup(ZSTR_VAL(func->common.function_name));
475 }
476
477 return retval;
478 } /* }}} */
479
cli_completion_generator_class(const char * text,size_t textlen,int * state)480 static char *cli_completion_generator_class(const char *text, size_t textlen, int *state) /* {{{ */
481 {
482 zend_class_entry *ce;
483 char *retval = cli_completion_generator_ht(text, textlen, state, EG(class_table), (void**)&ce);
484 if (retval) {
485 rl_completion_append_character = '\0';
486 retval = strdup(ZSTR_VAL(ce->name));
487 }
488
489 return retval;
490 } /* }}} */
491
cli_completion_generator_define(const char * text,size_t textlen,int * state,HashTable * ht)492 static char *cli_completion_generator_define(const char *text, size_t textlen, int *state, HashTable *ht) /* {{{ */
493 {
494 zend_class_entry **pce;
495 char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&pce);
496 if (retval) {
497 rl_completion_append_character = '\0';
498 retval = strdup(retval);
499 }
500
501 return retval;
502 } /* }}} */
503
504 static int cli_completion_state;
505
cli_completion_generator(const char * text,int index)506 static char *cli_completion_generator(const char *text, int index) /* {{{ */
507 {
508 /*
509 TODO:
510 - constants
511 - maybe array keys
512 - language constructs and other things outside a hashtable (echo, try, function, class, ...)
513 - object/class members
514
515 - future: respect scope ("php > function foo() { $[tab]" should only expand to local variables...)
516 */
517 char *retval = NULL;
518 size_t textlen = strlen(text);
519
520 if (!index) {
521 cli_completion_state = 0;
522 }
523 if (text[0] == '$') {
524 retval = cli_completion_generator_var(text, textlen, &cli_completion_state);
525 } else if (text[0] == '#' && text[1] != '[') {
526 retval = cli_completion_generator_ini(text, textlen, &cli_completion_state);
527 } else {
528 char *lc_text, *class_name_end;
529 zend_string *class_name = NULL;
530 zend_class_entry *ce = NULL;
531
532 class_name_end = strstr(text, "::");
533 if (class_name_end) {
534 size_t class_name_len = class_name_end - text;
535 class_name = zend_string_alloc(class_name_len, 0);
536 zend_str_tolower_copy(ZSTR_VAL(class_name), text, class_name_len);
537 if ((ce = zend_lookup_class(class_name)) == NULL) {
538 zend_string_release_ex(class_name, 0);
539 return NULL;
540 }
541 lc_text = zend_str_tolower_dup(class_name_end + 2, textlen - 2 - class_name_len);
542 textlen -= (class_name_len + 2);
543 } else {
544 lc_text = zend_str_tolower_dup(text, textlen);
545 }
546
547 switch (cli_completion_state) {
548 case 0:
549 case 1:
550 retval = cli_completion_generator_func(lc_text, textlen, &cli_completion_state, ce ? &ce->function_table : EG(function_table));
551 if (retval) {
552 break;
553 }
554 case 2:
555 case 3:
556 retval = cli_completion_generator_define(text, textlen, &cli_completion_state, ce ? &ce->constants_table : EG(zend_constants));
557 if (retval || ce) {
558 break;
559 }
560 case 4:
561 case 5:
562 retval = cli_completion_generator_class(lc_text, textlen, &cli_completion_state);
563 break;
564 default:
565 break;
566 }
567 efree(lc_text);
568 if (class_name) {
569 zend_string_release_ex(class_name, 0);
570 }
571 if (ce && retval) {
572 size_t len = ZSTR_LEN(ce->name) + 2 + strlen(retval) + 1;
573 char *tmp = malloc(len);
574
575 snprintf(tmp, len, "%s::%s", ZSTR_VAL(ce->name), retval);
576 free(retval);
577 retval = tmp;
578 }
579 }
580
581 return retval;
582 } /* }}} */
583
cli_code_completion(const char * text,int start,int end)584 static char **cli_code_completion(const char *text, int start, int end) /* {{{ */
585 {
586 return rl_completion_matches(text, cli_completion_generator);
587 }
588 /* }}} */
589
readline_shell_run(void)590 static int readline_shell_run(void) /* {{{ */
591 {
592 char *line;
593 size_t size = 4096, pos = 0, len;
594 char *code = emalloc(size);
595 zend_string *prompt = cli_get_prompt("php", '>');
596 char *history_file;
597 int history_lines_to_write = 0;
598
599 if (PG(auto_prepend_file) && PG(auto_prepend_file)[0]) {
600 zend_file_handle prepend_file;
601 zend_stream_init_filename(&prepend_file, PG(auto_prepend_file));
602 zend_execute_scripts(ZEND_REQUIRE, NULL, 1, &prepend_file);
603 }
604
605 #ifndef PHP_WIN32
606 history_file = tilde_expand("~/.php_history");
607 #else
608 spprintf(&history_file, MAX_PATH, "%s/.php_history", getenv("USERPROFILE"));
609 #endif
610 /* Install the default completion function for 'php -a'.
611 *
612 * But if readline_completion_function() was called by PHP code prior to the shell starting
613 * (e.g. with 'php -d auto_prepend_file=prepend.php -a'),
614 * then use that instead of PHP's default. */
615 if (rl_attempted_completion_function != php_readline_completion_cb) {
616 rl_attempted_completion_function = cli_code_completion;
617 }
618 #ifndef PHP_WIN32
619 rl_special_prefixes = "$";
620 #endif
621 read_history(history_file);
622
623 EG(exit_status) = 0;
624 while ((line = readline(ZSTR_VAL(prompt))) != NULL) {
625 if (strcmp(line, "exit") == 0 || strcmp(line, "quit") == 0) {
626 free(line);
627 break;
628 }
629
630 if (!pos && !*line) {
631 free(line);
632 continue;
633 }
634
635 len = strlen(line);
636
637 if (line[0] == '#' && line[1] != '[') {
638 char *param = strstr(&line[1], "=");
639 if (param) {
640 zend_string *cmd;
641 param++;
642 cmd = zend_string_init(&line[1], param - &line[1] - 1, 0);
643
644 zend_alter_ini_entry_chars_ex(cmd, param, strlen(param), PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0);
645 zend_string_release_ex(cmd, 0);
646 add_history(line);
647
648 zend_string_release_ex(prompt, 0);
649 /* TODO: This might be wrong! */
650 prompt = cli_get_prompt("php", '>');
651 continue;
652 }
653 }
654
655 if (pos + len + 2 > size) {
656 size = pos + len + 2;
657 code = erealloc(code, size);
658 }
659 memcpy(&code[pos], line, len);
660 pos += len;
661 code[pos] = '\n';
662 code[++pos] = '\0';
663
664 if (*line) {
665 add_history(line);
666 history_lines_to_write += 1;
667 }
668
669 free(line);
670 zend_string_release_ex(prompt, 0);
671
672 if (!cli_is_valid_code(code, pos, &prompt)) {
673 continue;
674 }
675
676 if (history_lines_to_write) {
677 #if HAVE_LIBEDIT
678 write_history(history_file);
679 #else
680 append_history(history_lines_to_write, history_file);
681 #endif
682 history_lines_to_write = 0;
683 }
684
685 zend_try {
686 zend_eval_stringl(code, pos, NULL, "php shell code");
687 } zend_end_try();
688
689 pos = 0;
690
691 if (!pager_pipe && php_last_char != '\0' && php_last_char != '\n') {
692 php_write("\n", 1);
693 }
694
695 if (EG(exception)) {
696 zend_exception_error(EG(exception), E_WARNING);
697 }
698
699 if (pager_pipe) {
700 fclose(pager_pipe);
701 pager_pipe = NULL;
702 }
703
704 php_last_char = '\0';
705 }
706 #ifdef PHP_WIN32
707 efree(history_file);
708 #else
709 free(history_file);
710 #endif
711 efree(code);
712 zend_string_release_ex(prompt, 0);
713 return EG(exit_status);
714 }
715 /* }}} */
716
717 #ifdef PHP_WIN32
718 typedef cli_shell_callbacks_t *(__cdecl *get_cli_shell_callbacks)(void);
719 #define GET_SHELL_CB(cb) \
720 do { \
721 get_cli_shell_callbacks get_callbacks; \
722 HMODULE hMod = GetModuleHandle("php.exe"); \
723 (cb) = NULL; \
724 if (strlen(sapi_module.name) >= 3 && 0 == strncmp("cli", sapi_module.name, 3)) { \
725 get_callbacks = (get_cli_shell_callbacks)GetProcAddress(hMod, "php_cli_get_shell_callbacks"); \
726 if (get_callbacks) { \
727 (cb) = get_callbacks(); \
728 } \
729 } \
730 } while(0)
731
732 #else
733 /*
734 #ifdef COMPILE_DL_READLINE
735 This dlsym() is always used as even the CGI SAPI is linked against "CLI"-only
736 extensions. If that is being changed dlsym() should only be used when building
737 this extension sharedto offer compatibility.
738 */
739 #define GET_SHELL_CB(cb) \
740 do { \
741 (cb) = NULL; \
742 cli_shell_callbacks_t *(*get_callbacks)(); \
743 get_callbacks = dlsym(RTLD_DEFAULT, "php_cli_get_shell_callbacks"); \
744 if (get_callbacks) { \
745 (cb) = get_callbacks(); \
746 } \
747 } while(0)
748 /*#else
749 #define GET_SHELL_CB(cb) (cb) = php_cli_get_shell_callbacks()
750 #endif*/
751 #endif
752
PHP_MINIT_FUNCTION(cli_readline)753 PHP_MINIT_FUNCTION(cli_readline)
754 {
755 cli_shell_callbacks_t *cb;
756
757 ZEND_INIT_MODULE_GLOBALS(cli_readline, cli_readline_init_globals, NULL);
758 REGISTER_INI_ENTRIES();
759
760 #if HAVE_LIBEDIT
761 REGISTER_STRING_CONSTANT("READLINE_LIB", "libedit", CONST_CS|CONST_PERSISTENT);
762 #else
763 REGISTER_STRING_CONSTANT("READLINE_LIB", "readline", CONST_CS|CONST_PERSISTENT);
764 #endif
765
766 GET_SHELL_CB(cb);
767 if (cb) {
768 cb->cli_shell_write = readline_shell_write;
769 cb->cli_shell_ub_write = readline_shell_ub_write;
770 cb->cli_shell_run = readline_shell_run;
771 }
772
773 return SUCCESS;
774 }
775
PHP_MSHUTDOWN_FUNCTION(cli_readline)776 PHP_MSHUTDOWN_FUNCTION(cli_readline)
777 {
778 cli_shell_callbacks_t *cb;
779
780 UNREGISTER_INI_ENTRIES();
781
782 GET_SHELL_CB(cb);
783 if (cb) {
784 cb->cli_shell_write = NULL;
785 cb->cli_shell_ub_write = NULL;
786 cb->cli_shell_run = NULL;
787 }
788
789 return SUCCESS;
790 }
791
PHP_MINFO_FUNCTION(cli_readline)792 PHP_MINFO_FUNCTION(cli_readline)
793 {
794 php_info_print_table_start();
795 php_info_print_table_header(2, "Readline Support", "enabled");
796 #ifdef PHP_WIN32
797 php_info_print_table_row(2, "Readline library", "WinEditLine");
798 #else
799 php_info_print_table_row(2, "Readline library", (rl_library_version ? rl_library_version : "Unknown"));
800 #endif
801 php_info_print_table_end();
802
803 DISPLAY_INI_ENTRIES();
804 }
805