xref: /PHP-8.0/ext/readline/readline_cli.c (revision 1fc961e2)
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