xref: /php-src/ext/readline/readline_cli.c (revision 3f0b204f)
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    | https://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 	bool unicode_warned = false;
139 
140 	do {
141 		if (*prompt_spec == '\\') {
142 			switch (prompt_spec[1]) {
143 			case '\\':
144 				smart_str_appendc(&retval, '\\');
145 				prompt_spec++;
146 				break;
147 			case 'n':
148 				smart_str_appendc(&retval, '\n');
149 				prompt_spec++;
150 				break;
151 			case 't':
152 				smart_str_appendc(&retval, '\t');
153 				prompt_spec++;
154 				break;
155 			case 'e':
156 				smart_str_appendc(&retval, '\033');
157 				prompt_spec++;
158 				break;
159 
160 
161 			case 'v':
162 				smart_str_appends(&retval, PHP_VERSION);
163 				prompt_spec++;
164 				break;
165 			case 'b':
166 				smart_str_appends(&retval, block);
167 				prompt_spec++;
168 				break;
169 			case '>':
170 				smart_str_appendc(&retval, prompt);
171 				prompt_spec++;
172 				break;
173 			case '`':
174 				smart_str_appendc(&retval, '`');
175 				prompt_spec++;
176 				break;
177 			default:
178 				smart_str_appendc(&retval, '\\');
179 				break;
180 			}
181 		} else if (*prompt_spec == '`') {
182 			char *prompt_end = strstr(prompt_spec + 1, "`");
183 			char *code;
184 
185 			if (prompt_end) {
186 				code = estrndup(prompt_spec + 1, prompt_end - prompt_spec - 1);
187 
188 				CLIR_G(prompt_str) = &retval;
189 				zend_try {
190 					zend_eval_stringl(code, prompt_end - prompt_spec - 1, NULL, "php prompt code");
191 				} zend_end_try();
192 				CLIR_G(prompt_str) = NULL;
193 				efree(code);
194 				prompt_spec = prompt_end;
195 			}
196 		} else {
197 			if (!(*prompt_spec & 0x80)) {
198 				smart_str_appendc(&retval, *prompt_spec);
199 			} else {
200 				if (!unicode_warned) {
201 					zend_error(E_WARNING,
202 						"prompt contains unsupported unicode characters");
203 					unicode_warned = true;
204 				}
205 				smart_str_appendc(&retval, '?');
206 			}
207 		}
208 	} while (++prompt_spec && *prompt_spec);
209 	smart_str_0(&retval);
210 	return retval.s;
211 }
212 /* }}} */
213 
cli_is_valid_code(char * code,size_t len,zend_string ** prompt)214 static int cli_is_valid_code(char *code, size_t len, zend_string **prompt) /* {{{ */
215 {
216 	int valid_end = 1, last_valid_end;
217 	int brackets_count = 0;
218 	int brace_count = 0;
219 	size_t i;
220 	php_code_type code_type = body;
221 	char *heredoc_tag = NULL;
222 	size_t heredoc_len;
223 
224 	for (i = 0; i < len; ++i) {
225 		switch(code_type) {
226 			default:
227 				switch(code[i]) {
228 					case '{':
229 						brackets_count++;
230 						valid_end = 0;
231 						break;
232 					case '}':
233 						if (brackets_count > 0) {
234 							brackets_count--;
235 						}
236 						valid_end = brackets_count ? 0 : 1;
237 						break;
238 					case '(':
239 						brace_count++;
240 						valid_end = 0;
241 						break;
242 					case ')':
243 						if (brace_count > 0) {
244 							brace_count--;
245 						}
246 						valid_end = 0;
247 						break;
248 					case ';':
249 						valid_end = brace_count == 0 && brackets_count == 0;
250 						break;
251 					case ' ':
252 					case '\r':
253 					case '\n':
254 					case '\t':
255 						break;
256 					case '\'':
257 						code_type = sstring;
258 						break;
259 					case '"':
260 						code_type = dstring;
261 						break;
262 					case '#':
263 						if (code[i+1] == '[') {
264 							valid_end = 0;
265 							break;
266 						}
267 						code_type = comment_line;
268 						break;
269 					case '/':
270 						if (code[i+1] == '/') {
271 							i++;
272 							code_type = comment_line;
273 							break;
274 						}
275 						if (code[i+1] == '*') {
276 							last_valid_end = valid_end;
277 							valid_end = 0;
278 							code_type = comment_block;
279 							i++;
280 							break;
281 						}
282 						valid_end = 0;
283 						break;
284 					case '?':
285 						if (code[i+1] == '>') {
286 							i++;
287 							code_type = outside;
288 							break;
289 						}
290 						valid_end = 0;
291 						break;
292 					case '<':
293 						valid_end = 0;
294 						if (i + 2 < len && code[i+1] == '<' && code[i+2] == '<') {
295 							i += 2;
296 							code_type = heredoc_start;
297 							heredoc_tag = NULL;
298 							heredoc_len = 0;
299 						}
300 						break;
301 					default:
302 						valid_end = 0;
303 						break;
304 				}
305 				break;
306 			case sstring:
307 				if (code[i] == '\\') {
308 					code_type = sstring_esc;
309 				} else {
310 					if (code[i] == '\'') {
311 						code_type = body;
312 					}
313 				}
314 				break;
315 			case sstring_esc:
316 				code_type = sstring;
317 				break;
318 			case dstring:
319 				if (code[i] == '\\') {
320 					code_type = dstring_esc;
321 				} else {
322 					if (code[i] == '"') {
323 						code_type = body;
324 					}
325 				}
326 				break;
327 			case dstring_esc:
328 				code_type = dstring;
329 				break;
330 			case comment_line:
331 				if (code[i] == '\n') {
332 					code_type = body;
333 				}
334 				break;
335 			case comment_block:
336 				if (code[i-1] == '*' && code[i] == '/') {
337 					code_type = body;
338 					valid_end = last_valid_end;
339 				}
340 				break;
341 			case heredoc_start:
342 				switch(code[i]) {
343 					case ' ':
344 					case '\t':
345 					case '\'':
346 						break;
347 					case '\r':
348 					case '\n':
349 						if (heredoc_tag) {
350 							code_type = heredoc;
351 						} else {
352 							/* Malformed heredoc without label */
353 							code_type = body;
354 						}
355 						break;
356 					default:
357 						if (!heredoc_tag) {
358 							heredoc_tag = code+i;
359 						}
360 						heredoc_len++;
361 						break;
362 				}
363 				break;
364 			case heredoc:
365 				ZEND_ASSERT(heredoc_tag);
366 				if (!strncmp(code + i - heredoc_len + 1, heredoc_tag, heredoc_len)) {
367 					unsigned char c = code[i + 1];
368 					char *p = code + i - heredoc_len;
369 
370 					if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c >= 0x80) break;
371 					while (*p == ' ' || *p == '\t') p--;
372 					if (*p != '\n') break;
373 					code_type = body;
374 				}
375 				break;
376 			case outside:
377 				if ((CG(short_tags) && !strncmp(code+i-1, "<?", 2))
378 				||  (i > 3 && !strncmp(code+i-4, "<?php", 5))
379 				) {
380 					code_type = body;
381 				}
382 				break;
383 		}
384 	}
385 
386 	switch (code_type) {
387 		default:
388 			if (brace_count) {
389 				*prompt = cli_get_prompt("php", '(');
390 			} else if (brackets_count) {
391 				*prompt = cli_get_prompt("php", '{');
392 			} else {
393 				*prompt = cli_get_prompt("php", '>');
394 			}
395 			break;
396 		case sstring:
397 		case sstring_esc:
398 			*prompt = cli_get_prompt("php", '\'');
399 			break;
400 		case dstring:
401 		case dstring_esc:
402 			*prompt = cli_get_prompt("php", '"');
403 			break;
404 		case comment_block:
405 			*prompt = cli_get_prompt("/* ", '>');
406 			break;
407 		case heredoc:
408 			*prompt = cli_get_prompt("<<<", '>');
409 			break;
410 		case outside:
411 			*prompt = cli_get_prompt("   ", '>');
412 			break;
413 	}
414 
415 	if (!valid_end || brackets_count) {
416 		return 0;
417 	} else {
418 		return 1;
419 	}
420 }
421 /* }}} */
422 
cli_completion_generator_ht(const char * text,size_t textlen,int * state,HashTable * ht,void ** pData)423 static char *cli_completion_generator_ht(const char *text, size_t textlen, int *state, HashTable *ht, void **pData) /* {{{ */
424 {
425 	zend_string *name;
426 	zend_ulong number;
427 
428 	if (!(*state % 2)) {
429 		zend_hash_internal_pointer_reset(ht);
430 		(*state)++;
431 	}
432 	while(zend_hash_has_more_elements(ht) == SUCCESS) {
433 		zend_hash_get_current_key(ht, &name, &number);
434 		if (!textlen || !strncmp(ZSTR_VAL(name), text, textlen)) {
435 			if (pData) {
436 				*pData = zend_hash_get_current_data_ptr(ht);
437 			}
438 			zend_hash_move_forward(ht);
439 			return ZSTR_VAL(name);
440 		}
441 		if (zend_hash_move_forward(ht) == FAILURE) {
442 			break;
443 		}
444 	}
445 	(*state)++;
446 	return NULL;
447 } /* }}} */
448 
cli_completion_generator_var(const char * text,size_t textlen,int * state)449 static char *cli_completion_generator_var(const char *text, size_t textlen, int *state) /* {{{ */
450 {
451 	char *retval, *tmp;
452 	zend_array *symbol_table = &EG(symbol_table);
453 
454 	tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, symbol_table, NULL);
455 	if (retval) {
456 		retval = malloc(strlen(tmp) + 2);
457 		retval[0] = '$';
458 		strcpy(&retval[1], tmp);
459 		rl_completion_append_character = '\0';
460 	}
461 	return retval;
462 } /* }}} */
463 
cli_completion_generator_ini(const char * text,size_t textlen,int * state)464 static char *cli_completion_generator_ini(const char *text, size_t textlen, int *state) /* {{{ */
465 {
466 	char *retval, *tmp;
467 
468 	tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, EG(ini_directives), NULL);
469 	if (retval) {
470 		retval = malloc(strlen(tmp) + 2);
471 		retval[0] = '#';
472 		strcpy(&retval[1], tmp);
473 		rl_completion_append_character = '=';
474 	}
475 	return retval;
476 } /* }}} */
477 
cli_completion_generator_func(const char * text,size_t textlen,int * state,HashTable * ht)478 static char *cli_completion_generator_func(const char *text, size_t textlen, int *state, HashTable *ht) /* {{{ */
479 {
480 	zend_function *func;
481 	char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&func);
482 	if (retval) {
483 		rl_completion_append_character = '(';
484 		retval = strdup(ZSTR_VAL(func->common.function_name));
485 	}
486 
487 	return retval;
488 } /* }}} */
489 
cli_completion_generator_class(const char * text,size_t textlen,int * state)490 static char *cli_completion_generator_class(const char *text, size_t textlen, int *state) /* {{{ */
491 {
492 	zend_class_entry *ce;
493 	char *retval = cli_completion_generator_ht(text, textlen, state, EG(class_table), (void**)&ce);
494 	if (retval) {
495 		rl_completion_append_character = '\0';
496 		retval = strdup(ZSTR_VAL(ce->name));
497 	}
498 
499 	return retval;
500 } /* }}} */
501 
cli_completion_generator_define(const char * text,size_t textlen,int * state,HashTable * ht)502 static char *cli_completion_generator_define(const char *text, size_t textlen, int *state, HashTable *ht) /* {{{ */
503 {
504 	zend_class_entry **pce;
505 	char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&pce);
506 	if (retval) {
507 		rl_completion_append_character = '\0';
508 		retval = strdup(retval);
509 	}
510 
511 	return retval;
512 } /* }}} */
513 
514 static int cli_completion_state;
515 
cli_completion_generator(const char * text,int index)516 static char *cli_completion_generator(const char *text, int index) /* {{{ */
517 {
518 /*
519 TODO:
520 - constants
521 - maybe array keys
522 - language constructs and other things outside a hashtable (echo, try, function, class, ...)
523 - object/class members
524 
525 - future: respect scope ("php > function foo() { $[tab]" should only expand to local variables...)
526 */
527 	char *retval = NULL;
528 	size_t textlen = strlen(text);
529 
530 	if (!index) {
531 		cli_completion_state = 0;
532 	}
533 	if (text[0] == '$') {
534 		retval = cli_completion_generator_var(text, textlen, &cli_completion_state);
535 	} else if (text[0] == '#' && text[1] != '[') {
536 		retval = cli_completion_generator_ini(text, textlen, &cli_completion_state);
537 	} else {
538 		char *lc_text, *class_name_end;
539 		zend_string *class_name = NULL;
540 		zend_class_entry *ce = NULL;
541 
542 		class_name_end = strstr(text, "::");
543 		if (class_name_end) {
544 			size_t class_name_len = class_name_end - text;
545 			class_name = zend_string_alloc(class_name_len, 0);
546 			zend_str_tolower_copy(ZSTR_VAL(class_name), text, class_name_len);
547 			if ((ce = zend_lookup_class(class_name)) == NULL) {
548 				zend_string_release_ex(class_name, 0);
549 				return NULL;
550 			}
551 			lc_text = zend_str_tolower_dup(class_name_end + 2, textlen - 2 - class_name_len);
552 			textlen -= (class_name_len + 2);
553 		} else {
554 			lc_text = zend_str_tolower_dup(text, textlen);
555 		}
556 
557 		switch (cli_completion_state) {
558 			case 0:
559 			case 1:
560 				retval = cli_completion_generator_func(lc_text, textlen, &cli_completion_state, ce ? &ce->function_table : EG(function_table));
561 				if (retval) {
562 					break;
563 				}
564 				ZEND_FALLTHROUGH;
565 			case 2:
566 			case 3:
567 				retval = cli_completion_generator_define(text, textlen, &cli_completion_state, ce ? &ce->constants_table : EG(zend_constants));
568 				if (retval || ce) {
569 					break;
570 				}
571 				ZEND_FALLTHROUGH;
572 			case 4:
573 			case 5:
574 				retval = cli_completion_generator_class(lc_text, textlen, &cli_completion_state);
575 				break;
576 			default:
577 				break;
578 		}
579 		efree(lc_text);
580 		if (class_name) {
581 			zend_string_release_ex(class_name, 0);
582 		}
583 		if (ce && retval) {
584 			size_t len = ZSTR_LEN(ce->name) + 2 + strlen(retval) + 1;
585 			char *tmp = malloc(len);
586 
587 			snprintf(tmp, len, "%s::%s", ZSTR_VAL(ce->name), retval);
588 			free(retval);
589 			retval = tmp;
590 		}
591 	}
592 
593 	return retval;
594 } /* }}} */
595 
cli_code_completion(const char * text,int start,int end)596 static char **cli_code_completion(const char *text, int start, int end) /* {{{ */
597 {
598 	return rl_completion_matches(text, cli_completion_generator);
599 }
600 /* }}} */
601 
readline_shell_run(void)602 static int readline_shell_run(void) /* {{{ */
603 {
604 	char *line;
605 	size_t size = 4096, pos = 0, len;
606 	char *code = emalloc(size);
607 	zend_string *prompt = cli_get_prompt("php", '>');
608 	char *history_file;
609 	int history_lines_to_write = 0;
610 
611 	if (PG(auto_prepend_file) && PG(auto_prepend_file)[0]) {
612 		zend_file_handle prepend_file;
613 
614 		zend_stream_init_filename(&prepend_file, PG(auto_prepend_file));
615 		zend_execute_scripts(ZEND_REQUIRE, NULL, 1, &prepend_file);
616 		zend_destroy_file_handle(&prepend_file);
617 	}
618 
619 #ifndef PHP_WIN32
620 	const char *histfile_env_name = "PHP_HISTFILE";
621 	if (getenv(histfile_env_name)) {
622 		spprintf(&history_file, MAXPATHLEN, "%s", getenv(histfile_env_name));
623 	} else {
624 		spprintf(&history_file, MAXPATHLEN, "%s/.php_history", getenv("HOME"));
625 	}
626 #else
627 	spprintf(&history_file, MAX_PATH, "%s/.php_history", getenv("USERPROFILE"));
628 #endif
629 	/* Install the default completion function for 'php -a'.
630 	 *
631 	 * But if readline_completion_function() was called by PHP code prior to the shell starting
632 	 * (e.g. with 'php -d auto_prepend_file=prepend.php -a'),
633 	 * then use that instead of PHP's default. */
634 	if (rl_attempted_completion_function != php_readline_completion_cb) {
635 		rl_attempted_completion_function = cli_code_completion;
636 	}
637 #ifndef PHP_WIN32
638 	rl_special_prefixes = "$";
639 #endif
640 	read_history(history_file);
641 
642 	EG(exit_status) = 0;
643 	while ((line = readline(ZSTR_VAL(prompt))) != NULL) {
644 		if (strcmp(line, "exit") == 0 || strcmp(line, "quit") == 0) {
645 			free(line);
646 			break;
647 		}
648 
649 		if (!pos && !*line) {
650 			free(line);
651 			continue;
652 		}
653 
654 		len = strlen(line);
655 
656 		if (line[0] == '#' && line[1] != '[') {
657 			char *param = strstr(&line[1], "=");
658 			if (param) {
659 				zend_string *cmd;
660 				param++;
661 				cmd = zend_string_init(&line[1], param - &line[1] - 1, 0);
662 
663 				zend_alter_ini_entry_chars_ex(cmd, param, strlen(param), PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0);
664 				zend_string_release_ex(cmd, 0);
665 				add_history(line);
666 
667 				zend_string_release_ex(prompt, 0);
668 				/* TODO: This might be wrong! */
669 				prompt = cli_get_prompt("php", '>');
670 				continue;
671 			}
672 		}
673 
674 		if (pos + len + 2 > size) {
675 			size = pos + len + 2;
676 			code = erealloc(code, size);
677 		}
678 		memcpy(&code[pos], line, len);
679 		pos += len;
680 		code[pos] = '\n';
681 		code[++pos] = '\0';
682 
683 		if (*line) {
684 			add_history(line);
685 			history_lines_to_write += 1;
686 		}
687 
688 		free(line);
689 		zend_string_release_ex(prompt, 0);
690 
691 		if (!cli_is_valid_code(code, pos, &prompt)) {
692 			continue;
693 		}
694 
695 		if (history_lines_to_write) {
696 #if HAVE_LIBEDIT
697 			write_history(history_file);
698 #else
699 			append_history(history_lines_to_write, history_file);
700 #endif
701 			history_lines_to_write = 0;
702 		}
703 
704 		zend_try {
705 			zend_eval_stringl(code, pos, NULL, "php shell code");
706 		} zend_end_try();
707 
708 		pos = 0;
709 
710 		if (!pager_pipe && php_last_char != '\0' && php_last_char != '\n') {
711 			php_write("\n", 1);
712 		}
713 
714 		if (EG(exception)) {
715 			zend_exception_error(EG(exception), E_WARNING);
716 		}
717 
718 		if (pager_pipe) {
719 			fclose(pager_pipe);
720 			pager_pipe = NULL;
721 		}
722 
723 		php_last_char = '\0';
724 	}
725 	efree(history_file);
726 	efree(code);
727 	zend_string_release_ex(prompt, 0);
728 	return EG(exit_status);
729 }
730 /* }}} */
731 
732 #ifdef PHP_WIN32
733 typedef cli_shell_callbacks_t *(__cdecl *get_cli_shell_callbacks)(void);
734 #define GET_SHELL_CB(cb) \
735 	do { \
736 		get_cli_shell_callbacks get_callbacks; \
737 		HMODULE hMod = GetModuleHandle("php.exe"); \
738 		(cb) = NULL; \
739 		if (strlen(sapi_module.name) >= 3 && 0 == strncmp("cli", sapi_module.name, 3)) { \
740 			get_callbacks = (get_cli_shell_callbacks)GetProcAddress(hMod, "php_cli_get_shell_callbacks"); \
741 			if (get_callbacks) { \
742 				(cb) = get_callbacks(); \
743 			} \
744 		} \
745 	} while(0)
746 
747 #else
748 /*
749 #ifdef COMPILE_DL_READLINE
750 This dlsym() is always used as even the CGI SAPI is linked against "CLI"-only
751 extensions. If that is being changed dlsym() should only be used when building
752 this extension sharedto offer compatibility.
753 */
754 #define GET_SHELL_CB(cb) \
755 	do { \
756 		(cb) = NULL; \
757 		cli_shell_callbacks_t *(*get_callbacks)(void); \
758 		get_callbacks = dlsym(RTLD_DEFAULT, "php_cli_get_shell_callbacks"); \
759 		if (get_callbacks) { \
760 			(cb) = get_callbacks(); \
761 		} \
762 	} while(0)
763 /*#else
764 #define GET_SHELL_CB(cb) (cb) = php_cli_get_shell_callbacks()
765 #endif*/
766 #endif
767 
PHP_MINIT_FUNCTION(cli_readline)768 PHP_MINIT_FUNCTION(cli_readline)
769 {
770 	cli_shell_callbacks_t *cb;
771 
772 	ZEND_INIT_MODULE_GLOBALS(cli_readline, cli_readline_init_globals, NULL);
773 	REGISTER_INI_ENTRIES();
774 
775 	GET_SHELL_CB(cb);
776 	if (cb) {
777 		cb->cli_shell_write = readline_shell_write;
778 		cb->cli_shell_ub_write = readline_shell_ub_write;
779 		cb->cli_shell_run = readline_shell_run;
780 	}
781 
782 	return SUCCESS;
783 }
784 
PHP_MSHUTDOWN_FUNCTION(cli_readline)785 PHP_MSHUTDOWN_FUNCTION(cli_readline)
786 {
787 	cli_shell_callbacks_t *cb;
788 
789 	UNREGISTER_INI_ENTRIES();
790 
791 	GET_SHELL_CB(cb);
792 	if (cb) {
793 		cb->cli_shell_write = NULL;
794 		cb->cli_shell_ub_write = NULL;
795 		cb->cli_shell_run = NULL;
796 	}
797 
798 	return SUCCESS;
799 }
800 
PHP_MINFO_FUNCTION(cli_readline)801 PHP_MINFO_FUNCTION(cli_readline)
802 {
803 	php_info_print_table_start();
804 	php_info_print_table_row(2, "Readline Support", "enabled");
805 #ifdef PHP_WIN32
806 	php_info_print_table_row(2, "Readline library", "WinEditLine");
807 #else
808 	php_info_print_table_row(2, "Readline library", (rl_library_version ? rl_library_version : "Unknown"));
809 #endif
810 	php_info_print_table_end();
811 
812 	DISPLAY_INI_ENTRIES();
813 }
814