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