xref: /PHP-7.4/ext/readline/readline_cli.c (revision 03c7749d)
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