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