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