xref: /PHP-8.3/sapi/phpdbg/phpdbg_cmd.c (revision dc670cb7)
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    | Authors: Felipe Pena <felipe@php.net>                                |
14    | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
15    | Authors: Bob Weinand <bwoebi@php.net>                                |
16    +----------------------------------------------------------------------+
17 */
18 
19 #include "phpdbg.h"
20 #include "phpdbg_cmd.h"
21 #include "phpdbg_utils.h"
22 #include "phpdbg_set.h"
23 #include "phpdbg_prompt.h"
24 #include "phpdbg_io.h"
25 
26 #ifdef HAVE_UNISTD_H
27 # include <unistd.h>
28 #endif
29 
ZEND_EXTERN_MODULE_GLOBALS(phpdbg)30 ZEND_EXTERN_MODULE_GLOBALS(phpdbg)
31 
32 static inline const char *phpdbg_command_name(const phpdbg_command_t *command, char *buffer) {
33 	size_t pos = 0;
34 
35 	if (command->parent) {
36 		memcpy(&buffer[pos], command->parent->name, command->parent->name_len);
37 		pos += command->parent->name_len;
38 		memcpy(&buffer[pos], " ", sizeof(" ")-1);
39 		pos += (sizeof(" ")-1);
40 	}
41 
42 	memcpy(&buffer[pos], command->name, command->name_len);
43 	pos += command->name_len;
44 	buffer[pos] = 0;
45 
46 	return buffer;
47 }
48 
phpdbg_get_param_type(const phpdbg_param_t * param)49 PHPDBG_API const char *phpdbg_get_param_type(const phpdbg_param_t *param) /* {{{ */
50 {
51 	switch (param->type) {
52 		case STACK_PARAM:
53 			return "stack";
54 		case EMPTY_PARAM:
55 			return "empty";
56 		case ADDR_PARAM:
57 			return "address";
58 		case NUMERIC_PARAM:
59 			return "numeric";
60 		case METHOD_PARAM:
61 			return "method";
62 		case NUMERIC_FUNCTION_PARAM:
63 			return "function opline";
64 		case NUMERIC_METHOD_PARAM:
65 			return "method opline";
66 		case FILE_PARAM:
67 			return "file or file opline";
68 		case STR_PARAM:
69 			return "string";
70 		default: /* this is bad */
71 			return "unknown";
72 	}
73 }
74 
phpdbg_clear_param(phpdbg_param_t * param)75 PHPDBG_API void phpdbg_clear_param(phpdbg_param_t *param) /* {{{ */
76 {
77 	if (param) {
78 		switch (param->type) {
79 			case FILE_PARAM:
80 				efree(param->file.name);
81 				break;
82 			case METHOD_PARAM:
83 				efree(param->method.class);
84 				efree(param->method.name);
85 				break;
86 			case STR_PARAM:
87 				efree(param->str);
88 				break;
89 			default:
90 				break;
91 		}
92 	}
93 
94 } /* }}} */
95 
phpdbg_param_tostring(const phpdbg_param_t * param,char ** pointer)96 PHPDBG_API char* phpdbg_param_tostring(const phpdbg_param_t *param, char **pointer) /* {{{ */
97 {
98 	switch (param->type) {
99 		case STR_PARAM:
100 			ZEND_IGNORE_VALUE(asprintf(pointer, "%s", param->str));
101 		break;
102 
103 		case ADDR_PARAM:
104 			ZEND_IGNORE_VALUE(asprintf(pointer, ZEND_ULONG_FMT, param->addr));
105 		break;
106 
107 		case NUMERIC_PARAM:
108 			ZEND_IGNORE_VALUE(asprintf(pointer, ZEND_LONG_FMT, param->num));
109 		break;
110 
111 		case METHOD_PARAM:
112 			ZEND_IGNORE_VALUE(asprintf(pointer, "%s::%s", param->method.class, param->method.name));
113 		break;
114 
115 		case FILE_PARAM:
116 			if (param->num) {
117 				ZEND_IGNORE_VALUE(asprintf(pointer, "%s:"ZEND_ULONG_FMT"#"ZEND_ULONG_FMT, param->file.name, param->file.line, param->num));
118 			} else {
119 				ZEND_IGNORE_VALUE(asprintf(pointer, "%s:"ZEND_ULONG_FMT, param->file.name, param->file.line));
120 			}
121 		break;
122 
123 		case NUMERIC_FUNCTION_PARAM:
124 			ZEND_IGNORE_VALUE(asprintf(pointer, "%s#"ZEND_ULONG_FMT, param->str, param->num));
125 		break;
126 
127 		case NUMERIC_METHOD_PARAM:
128 			ZEND_IGNORE_VALUE(asprintf(pointer, "%s::%s#"ZEND_ULONG_FMT, param->method.class, param->method.name, param->num));
129 		break;
130 
131 		default:
132 			*pointer = strdup("unknown");
133 	}
134 
135 	return *pointer;
136 } /* }}} */
137 
phpdbg_copy_param(const phpdbg_param_t * src,phpdbg_param_t * dest)138 PHPDBG_API void phpdbg_copy_param(const phpdbg_param_t* src, phpdbg_param_t* dest) /* {{{ */
139 {
140 	switch ((dest->type = src->type)) {
141 		case STACK_PARAM:
142 			/* nope */
143 		break;
144 
145 		case STR_PARAM:
146 			dest->str = estrndup(src->str, src->len);
147 			dest->len = src->len;
148 		break;
149 
150 		case OP_PARAM:
151 			dest->str = estrndup(src->str, src->len);
152 			dest->len = src->len;
153 		break;
154 
155 		case ADDR_PARAM:
156 			dest->addr = src->addr;
157 		break;
158 
159 		case NUMERIC_PARAM:
160 			dest->num = src->num;
161 		break;
162 
163 		case METHOD_PARAM:
164 			dest->method.class = estrdup(src->method.class);
165 			dest->method.name = estrdup(src->method.name);
166 		break;
167 
168 		case NUMERIC_FILE_PARAM:
169 		case FILE_PARAM:
170 			dest->file.name = estrdup(src->file.name);
171 			dest->file.line = src->file.line;
172 			if (src->num)
173 				dest->num   = src->num;
174 		break;
175 
176 		case NUMERIC_FUNCTION_PARAM:
177 			dest->str = estrndup(src->str, src->len);
178 			dest->num = src->num;
179 			dest->len = src->len;
180 		break;
181 
182 		case NUMERIC_METHOD_PARAM:
183 			dest->method.class = estrdup(src->method.class);
184 			dest->method.name = estrdup(src->method.name);
185 			dest->num = src->num;
186 		break;
187 
188 		case EMPTY_PARAM: { /* do nothing */ } break;
189 
190 		default: {
191 			/* not yet */
192 		}
193 	}
194 } /* }}} */
195 
phpdbg_hash_param(const phpdbg_param_t * param)196 PHPDBG_API zend_ulong phpdbg_hash_param(const phpdbg_param_t *param) /* {{{ */
197 {
198 	zend_ulong hash = param->type;
199 
200 	switch (param->type) {
201 		case STACK_PARAM:
202 			/* nope */
203 		break;
204 
205 		case STR_PARAM:
206 			hash += zend_hash_func(param->str, param->len);
207 		break;
208 
209 		case METHOD_PARAM:
210 			hash += zend_hash_func(param->method.class, strlen(param->method.class));
211 			hash += zend_hash_func(param->method.name, strlen(param->method.name));
212 		break;
213 
214 		case FILE_PARAM:
215 			hash += zend_hash_func(param->file.name, strlen(param->file.name));
216 			hash += param->file.line;
217 			if (param->num)
218 				hash += param->num;
219 		break;
220 
221 		case ADDR_PARAM:
222 			hash += param->addr;
223 		break;
224 
225 		case NUMERIC_PARAM:
226 			hash += param->num;
227 		break;
228 
229 		case NUMERIC_FUNCTION_PARAM:
230 			hash += zend_hash_func(param->str, param->len);
231 			hash += param->num;
232 		break;
233 
234 		case NUMERIC_METHOD_PARAM:
235 			hash += zend_hash_func(param->method.class, strlen(param->method.class));
236 			hash += zend_hash_func(param->method.name, strlen(param->method.name));
237 			if (param->num)
238 				hash+= param->num;
239 		break;
240 
241 		case EMPTY_PARAM: { /* do nothing */ } break;
242 
243 		default: {
244 			/* not yet */
245 		}
246 	}
247 
248 	return hash;
249 } /* }}} */
250 
phpdbg_match_param(const phpdbg_param_t * l,const phpdbg_param_t * r)251 PHPDBG_API bool phpdbg_match_param(const phpdbg_param_t *l, const phpdbg_param_t *r) /* {{{ */
252 {
253 	if (l && r) {
254 		if (l->type == r->type) {
255 			switch (l->type) {
256 				case STACK_PARAM:
257 					/* nope, or yep */
258 					return 1;
259 				break;
260 
261 				case NUMERIC_FUNCTION_PARAM:
262 					if (l->num != r->num) {
263 						break;
264 					}
265 				ZEND_FALLTHROUGH;
266 
267 				case STR_PARAM:
268 					return (l->len == r->len) &&
269 							(memcmp(l->str, r->str, l->len) == SUCCESS);
270 
271 				case NUMERIC_PARAM:
272 					return (l->num == r->num);
273 
274 				case ADDR_PARAM:
275 					return (l->addr == r->addr);
276 
277 				case FILE_PARAM: {
278 					if (l->file.line == r->file.line) {
279 						size_t lengths[2] = {
280 							strlen(l->file.name), strlen(r->file.name)};
281 
282 						if (lengths[0] == lengths[1]) {
283 							if ((!l->num && !r->num) || (l->num == r->num)) {
284 								return (memcmp(
285 									l->file.name, r->file.name, lengths[0]) == SUCCESS);
286 							}
287 						}
288 					}
289 				} break;
290 
291 				case NUMERIC_METHOD_PARAM:
292 					if (l->num != r->num) {
293 						break;
294 					}
295 				ZEND_FALLTHROUGH;
296 
297 				case METHOD_PARAM: {
298 					size_t lengths[2] = {
299 						strlen(l->method.class), strlen(r->method.class)};
300 					if (lengths[0] == lengths[1]) {
301 						if (memcmp(l->method.class, r->method.class, lengths[0]) == SUCCESS) {
302 							lengths[0] = strlen(l->method.name);
303 							lengths[1] = strlen(r->method.name);
304 
305 							if (lengths[0] == lengths[1]) {
306 								return (memcmp(
307 									l->method.name, r->method.name, lengths[0]) == SUCCESS);
308 							}
309 						}
310 					}
311 				} break;
312 
313 				case EMPTY_PARAM:
314 					return 1;
315 
316 				default: {
317 					/* not yet */
318 				}
319 			}
320 		}
321 	}
322 	return 0;
323 } /* }}} */
324 
325 /* {{{ */
phpdbg_param_debug(const phpdbg_param_t * param,const char * msg)326 PHPDBG_API void phpdbg_param_debug(const phpdbg_param_t *param, const char *msg) {
327 	if (param && param->type) {
328 		switch (param->type) {
329 			case STR_PARAM:
330 				fprintf(stderr, "%s STR_PARAM(%s=%zu)\n", msg, param->str, param->len);
331 			break;
332 
333 			case ADDR_PARAM:
334 				fprintf(stderr, "%s ADDR_PARAM(" ZEND_ULONG_FMT ")\n", msg, param->addr);
335 			break;
336 
337 			case NUMERIC_FILE_PARAM:
338 				fprintf(stderr, "%s NUMERIC_FILE_PARAM(%s:#"ZEND_ULONG_FMT")\n", msg, param->file.name, param->file.line);
339 			break;
340 
341 			case FILE_PARAM:
342 				fprintf(stderr, "%s FILE_PARAM(%s:"ZEND_ULONG_FMT")\n", msg, param->file.name, param->file.line);
343 			break;
344 
345 			case METHOD_PARAM:
346 				fprintf(stderr, "%s METHOD_PARAM(%s::%s)\n", msg, param->method.class, param->method.name);
347 			break;
348 
349 			case NUMERIC_METHOD_PARAM:
350 				fprintf(stderr, "%s NUMERIC_METHOD_PARAM(%s::%s)\n", msg, param->method.class, param->method.name);
351 			break;
352 
353 			case NUMERIC_FUNCTION_PARAM:
354 				fprintf(stderr, "%s NUMERIC_FUNCTION_PARAM(%s::"ZEND_LONG_FMT")\n", msg, param->str, param->num);
355 			break;
356 
357 			case NUMERIC_PARAM:
358 				fprintf(stderr, "%s NUMERIC_PARAM("ZEND_LONG_FMT")\n", msg, param->num);
359 			break;
360 
361 			case COND_PARAM:
362 				fprintf(stderr, "%s COND_PARAM(%s=%zu)\n", msg, param->str, param->len);
363 			break;
364 
365 			case OP_PARAM:
366 				fprintf(stderr, "%s OP_PARAM(%s=%zu)\n", msg, param->str, param->len);
367 			break;
368 
369 			default: {
370 				/* not yet */
371 			}
372 		}
373 	}
374 } /* }}} */
375 
376 /* {{{ */
phpdbg_stack_free(phpdbg_param_t * stack)377 PHPDBG_API void phpdbg_stack_free(phpdbg_param_t *stack) {
378 	ZEND_ASSERT(stack != NULL);
379 
380 	if (stack->next) {
381 		phpdbg_param_t *remove = stack->next;
382 
383 		while (remove) {
384 			phpdbg_param_t *next = NULL;
385 
386 			if (remove->next)
387 				next = remove->next;
388 
389 			switch (remove->type) {
390 				case NUMERIC_METHOD_PARAM:
391 				case METHOD_PARAM:
392 					if (remove->method.class) {
393 						efree(remove->method.class);
394 					}
395 					if (remove->method.name) {
396 						efree(remove->method.name);
397 					}
398 				break;
399 
400 				case NUMERIC_FUNCTION_PARAM:
401 				case STR_PARAM:
402 				case OP_PARAM:
403 				case EVAL_PARAM:
404 				case SHELL_PARAM:
405 				case COND_PARAM:
406 				case RUN_PARAM:
407 					if (remove->str) {
408 						efree(remove->str);
409 					}
410 				break;
411 
412 				case NUMERIC_FILE_PARAM:
413 				case FILE_PARAM:
414 					if (remove->file.name) {
415 						efree(remove->file.name);
416 					}
417 				break;
418 
419 				default: {
420 					/* nothing */
421 				}
422 			}
423 
424 			free(remove);
425 			remove = NULL;
426 
427 			if (next)
428 				remove = next;
429 			else break;
430 		}
431 
432 		stack->next = NULL;
433 	}
434 } /* }}} */
435 
436 /* {{{ */
phpdbg_stack_push(phpdbg_param_t * stack,phpdbg_param_t * param)437 PHPDBG_API void phpdbg_stack_push(phpdbg_param_t *stack, phpdbg_param_t *param) {
438 	phpdbg_param_t *next = calloc(1, sizeof(phpdbg_param_t));
439 
440 	if (!next) {
441 		return;
442 	}
443 
444 	*(next) = *(param);
445 
446 	next->next = NULL;
447 
448 	if (stack->top == NULL) {
449 		stack->top = next;
450 		next->top = NULL;
451 		stack->next = next;
452 	} else {
453 		stack->top->next = next;
454 		next->top = stack->top;
455 		stack->top = next;
456 	}
457 
458 	stack->len++;
459 } /* }}} */
460 
461 /* {{{ */
phpdbg_stack_separate(phpdbg_param_t * param)462 PHPDBG_API void phpdbg_stack_separate(phpdbg_param_t *param) {
463 	phpdbg_param_t *stack = calloc(1, sizeof(phpdbg_param_t));
464 
465 	stack->type = STACK_PARAM;
466 	stack->next = param->next;
467 	param->next = stack;
468 	stack->top = param->top;
469 } /* }}} */
470 
phpdbg_stack_verify(const phpdbg_command_t * command,phpdbg_param_t ** stack)471 PHPDBG_API int phpdbg_stack_verify(const phpdbg_command_t *command, phpdbg_param_t **stack) {
472 	if (command) {
473 		char buffer[128] = {0,};
474 		const phpdbg_param_t *top = (stack != NULL) ? *stack : NULL;
475 		const char *arg = command->args;
476 		zend_ulong least = 0L,
477 		           received = 0L,
478 		           current = 0L;
479 		bool optional = 0;
480 
481 		/* check for arg spec */
482 		if (!(arg) || !(*arg)) {
483 			if (!top || top->type == STACK_PARAM) {
484 				return SUCCESS;
485 			}
486 
487 			phpdbg_error("The command \"%s\" expected no arguments",
488 				phpdbg_command_name(command, buffer));
489 			return FAILURE;
490 		}
491 
492 		least = 0L;
493 
494 		/* count least amount of arguments */
495 		while (arg && *arg) {
496 			if (arg[0] == '|') {
497 				break;
498 			}
499 			least++;
500 			arg++;
501 		}
502 
503 		arg = command->args;
504 
505 #define verify_arg(e, a, t) if (!(a)) { \
506 	if (!optional) { \
507 		phpdbg_error("The command \"%s\" expected %s and got nothing at parameter "ZEND_ULONG_FMT, \
508 			phpdbg_command_name(command, buffer), \
509 			(e), \
510 			current); \
511 		return FAILURE;\
512 	} \
513 } else if ((a)->type != (t)) { \
514 	phpdbg_error("The command \"%s\" expected %s and got %s at parameter "ZEND_ULONG_FMT, \
515 		phpdbg_command_name(command, buffer), \
516 		(e),\
517 		phpdbg_get_param_type((a)), \
518 		current); \
519 	return FAILURE; \
520 }
521 
522 		while (arg && *arg) {
523 			if (top && top->type == STACK_PARAM) {
524 				break;
525 			}
526 
527 			current++;
528 
529 			switch (*arg) {
530 				case '|': {
531 					current--;
532 					optional = 1;
533 					arg++;
534 				} continue;
535 
536 				case 'i': verify_arg("raw input", top, STR_PARAM); break;
537 				case 's': verify_arg("string", top, STR_PARAM); break;
538 				case 'n': verify_arg("number", top, NUMERIC_PARAM); break;
539 				case 'm': verify_arg("method", top, METHOD_PARAM); break;
540 				case 'a': verify_arg("address", top, ADDR_PARAM); break;
541 				case 'f': verify_arg("file:line", top, FILE_PARAM); break;
542 				case 'c': verify_arg("condition", top, COND_PARAM); break;
543 				case 'o': verify_arg("opcode", top, OP_PARAM); break;
544 				case 'b': verify_arg("boolean", top, NUMERIC_PARAM); break;
545 
546 				case '*': { /* do nothing */ } break;
547 			}
548 
549 			if (top) {
550 				top = top->next;
551 			} else {
552 				break;
553 			}
554 
555 			received++;
556 			arg++;
557 		}
558 
559 #undef verify_arg
560 
561 		if ((received < least)) {
562 			phpdbg_error("The command \"%s\" expected at least "ZEND_ULONG_FMT" arguments (%s) and received "ZEND_ULONG_FMT,
563 				phpdbg_command_name(command, buffer),
564 				least,
565 				command->args,
566 				received);
567 			return FAILURE;
568 		}
569 	}
570 
571 	return SUCCESS;
572 }
573 
574 /* {{{ */
phpdbg_stack_resolve(const phpdbg_command_t * commands,const phpdbg_command_t * parent,phpdbg_param_t ** top)575 PHPDBG_API const phpdbg_command_t *phpdbg_stack_resolve(const phpdbg_command_t *commands, const phpdbg_command_t *parent, phpdbg_param_t **top) {
576 	const phpdbg_command_t *command = commands;
577 	phpdbg_param_t *name = *top;
578 	const phpdbg_command_t *matched[3] = {NULL, NULL, NULL};
579 	zend_ulong matches = 0L;
580 
581 	while (command && command->name && command->handler) {
582 		if (name->len == 1 || command->name_len >= name->len) {
583 			/* match single letter alias */
584 			if (command->alias && (name->len == 1)) {
585 				if (command->alias == (*name->str)) {
586 					matched[matches] = command;
587 					matches++;
588 				}
589 			} else {
590 				/* match full, case insensitive, command name */
591 				if (strncasecmp(command->name, name->str, name->len) == SUCCESS) {
592 					if (matches < 3) {
593 						/* only allow abbreviating commands that can be aliased */
594 						if ((name->len != command->name_len && command->alias) || name->len == command->name_len) {
595 							matched[matches] = command;
596 							matches++;
597 						}
598 
599 						/* exact match */
600 						if (name->len == command->name_len) {
601 							break;
602 						}
603 					} else {
604 						break;
605 					}
606 				}
607 			}
608 		}
609 
610 		command++;
611 	}
612 
613 	switch (matches) {
614 		case 0:
615 			if (parent) {
616 				phpdbg_error("The command \"%s %s\" could not be found", parent->name, name->str);
617 			} else {
618 				phpdbg_error("The command \"%s\" could not be found", name->str);
619 			}
620 			return parent;
621 
622 		case 1:
623 			(*top) = (*top)->next;
624 
625 			command = matched[0];
626 			break;
627 
628 		default: {
629 			char *list = NULL;
630 			uint32_t it = 0;
631 			size_t pos = 0;
632 
633 			while (it < matches) {
634 				if (!list) {
635 					list = emalloc(matched[it]->name_len + 1 + (it + 1 < matches ? sizeof(", ") - 1 : 0));
636 				} else {
637 					list = erealloc(list, (pos + matched[it]->name_len) + 1 + (it + 1 < matches ? sizeof(", ") - 1 : 0));
638 				}
639 				memcpy(&list[pos], matched[it]->name, matched[it]->name_len);
640 				pos += matched[it]->name_len;
641 				if ((it + 1) < matches) {
642 					memcpy(&list[pos], ", ", sizeof(", ") - 1);
643 					pos += (sizeof(", ") - 1);
644 				}
645 
646 				list[pos] = 0;
647 				it++;
648 			}
649 
650 			/* ", " separated matches */
651 			phpdbg_error("The command \"%s\" is ambiguous, matching "ZEND_ULONG_FMT" commands (%s)", name->str, matches, list);
652 			efree(list);
653 
654 			return NULL;
655 		}
656 	}
657 
658 	if (command->subs && (*top) && ((*top)->type == STR_PARAM)) {
659 		return phpdbg_stack_resolve(command->subs, command, top);
660 	} else {
661 		return command;
662 	}
663 
664 	return NULL;
665 } /* }}} */
666 
phpdbg_internal_stack_execute(phpdbg_param_t * stack,bool allow_async_unsafe)667 static int phpdbg_internal_stack_execute(phpdbg_param_t *stack, bool allow_async_unsafe) {
668 	const phpdbg_command_t *handler = NULL;
669 	phpdbg_param_t *top = (phpdbg_param_t *) stack->next;
670 
671 	switch (top->type) {
672 		case EVAL_PARAM:
673 			phpdbg_activate_err_buf(0);
674 			phpdbg_free_err_buf();
675 			return PHPDBG_COMMAND_HANDLER(ev)(top);
676 
677 		case RUN_PARAM:
678 			if (!allow_async_unsafe) {
679 				phpdbg_error("run command is disallowed during hard interrupt");
680 			}
681 			phpdbg_activate_err_buf(0);
682 			phpdbg_free_err_buf();
683 			return PHPDBG_COMMAND_HANDLER(run)(top);
684 
685 		case SHELL_PARAM:
686 			if (!allow_async_unsafe) {
687 				phpdbg_error("sh command is disallowed during hard interrupt");
688 				return FAILURE;
689 			}
690 			phpdbg_activate_err_buf(0);
691 			phpdbg_free_err_buf();
692 			return PHPDBG_COMMAND_HANDLER(sh)(top);
693 
694 		case STR_PARAM: {
695 			handler = phpdbg_stack_resolve(phpdbg_prompt_commands, NULL, &top);
696 
697 			if (handler) {
698 				if (!allow_async_unsafe && !(handler->flags & PHPDBG_ASYNC_SAFE)) {
699 					phpdbg_error("%s command is disallowed during hard interrupt", handler->name);
700 					return FAILURE;
701 				}
702 
703 				if (phpdbg_stack_verify(handler, &top) == SUCCESS) {
704 					phpdbg_activate_err_buf(0);
705 					phpdbg_free_err_buf();
706 					return handler->handler(top);
707 				}
708 			}
709 		} return FAILURE;
710 
711 		default:
712 			phpdbg_error("The first parameter makes no sense !");
713 			return FAILURE;
714 	}
715 
716 	return SUCCESS;
717 } /* }}} */
718 
719 /* {{{ */
phpdbg_stack_execute(phpdbg_param_t * stack,bool allow_async_unsafe)720 PHPDBG_API int phpdbg_stack_execute(phpdbg_param_t *stack, bool allow_async_unsafe) {
721 	phpdbg_param_t *top = stack;
722 
723 	if (stack->type != STACK_PARAM) {
724 		phpdbg_error("The passed argument was not a stack !");
725 		return FAILURE;
726 	}
727 
728 	if (!stack->len) {
729 		phpdbg_error("The stack contains nothing !");
730 		return FAILURE;
731 	}
732 
733 	do {
734 		if (top->type == STACK_PARAM) {
735 			int result;
736 			if ((result = phpdbg_internal_stack_execute(top, allow_async_unsafe)) != SUCCESS) {
737 				return result;
738 			}
739 		}
740 	} while ((top = top->next));
741 
742 	return SUCCESS;
743 } /* }}} */
744 
phpdbg_read_input(const char * buffered)745 PHPDBG_API char *phpdbg_read_input(const char *buffered) /* {{{ */
746 {
747 	char buf[PHPDBG_MAX_CMD];
748 	char *buffer = NULL;
749 
750 	if ((PHPDBG_G(flags) & (PHPDBG_IS_STOPPING | PHPDBG_IS_RUNNING)) != PHPDBG_IS_STOPPING) {
751 		if (buffered == NULL) {
752 #ifdef HAVE_PHPDBG_READLINE
753 # ifdef HAVE_UNISTD_H
754 			/* EOF makes readline write prompt again in local console mode and
755 			ignored if compiled without readline integration. */
756 			if (!isatty(PHPDBG_G(io)[PHPDBG_STDIN].fd)) {
757 				char buf[PHPDBG_MAX_CMD];
758 				phpdbg_write("%s", phpdbg_get_prompt());
759 				phpdbg_consume_stdin_line(buf);
760 				buffer = estrdup(buf);
761 			} else
762 # endif
763 			{
764 				char *cmd = readline(phpdbg_get_prompt());
765 				PHPDBG_G(last_was_newline) = 1;
766 
767 				if (!cmd) {
768 					PHPDBG_G(flags) |= PHPDBG_IS_QUITTING;
769 					zend_bailout();
770 				}
771 
772 				add_history(cmd);
773 				buffer = estrdup(cmd);
774 				free(cmd);
775 			}
776 #else
777 			phpdbg_write("%s", phpdbg_get_prompt());
778 			phpdbg_consume_stdin_line(buf);
779 			buffer = estrdup(buf);
780 #endif
781 		} else {
782 			buffer = estrdup(buffered);
783 		}
784 	}
785 
786 	if (buffer && isspace(*buffer)) {
787 		char *trimmed = buffer;
788 		while (isspace(*trimmed))
789 			trimmed++;
790 
791 		trimmed = estrdup(trimmed);
792 		efree(buffer);
793 		buffer = trimmed;
794 	}
795 
796 	if (buffer && strlen(buffer)) {
797 		if (PHPDBG_G(buffer)) {
798 			free(PHPDBG_G(buffer));
799 		}
800 		PHPDBG_G(buffer) = strdup(buffer);
801 	} else if (PHPDBG_G(buffer)) {
802 		if (buffer) {
803 			efree(buffer);
804 		}
805 		buffer = estrdup(PHPDBG_G(buffer));
806 	}
807 
808 	return buffer;
809 } /* }}} */
810 
phpdbg_destroy_input(char ** input)811 PHPDBG_API void phpdbg_destroy_input(char **input) /*{{{ */
812 {
813 	efree(*input);
814 } /* }}} */
815 
phpdbg_ask_user_permission(const char * question)816 PHPDBG_API int phpdbg_ask_user_permission(const char *question) {
817 	char buf[PHPDBG_MAX_CMD];
818 	phpdbg_out("%s", question);
819 	phpdbg_out(" (type y or n): ");
820 
821 	while (1) {
822 		phpdbg_consume_stdin_line(buf);
823 		if ((buf[1] == '\n' || (buf[1] == '\r' && buf[2] == '\n')) && (buf[0] == 'y' || buf[0] == 'n')) {
824 			if (buf[0] == 'y') {
825 				return SUCCESS;
826 			}
827 			return FAILURE;
828 		}
829 		phpdbg_out("Please enter either y (yes) or n (no): ");
830 	}
831 
832 	return SUCCESS;
833 }
834