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